From 007f934b45437ae29a7f2250acb298eefd625227 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 18 Sep 2025 17:45:21 +0200 Subject: [PATCH 01/84] initial changes for C++ 20 support --- CMakeLists.txt | 9 +++++---- conan/mruby/conanfile.py | 2 +- conan/profiles/xcode | 2 +- conanfile.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29b337cb9..8e44023a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,13 +22,14 @@ set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATADIR}/mtconnect") message(INFO " Shared build: ${SHARED_AGENT_LIB}") -project(cppagent LANGUAGES C CXX) - # We will define these properties by default for each CMake target to be created. -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CXX_COMPILE_FEATURES cxx_std_17) +set(CXX_COMPILE_FEATURES cxx_std_20) +set(CMAKE_OSX_DEPLOYMENT_TARGET 13.3) + +project(cppagent LANGUAGES C CXX) set(WITH_RUBY ON CACHE STRING "With Ruby Support") diff --git a/conan/mruby/conanfile.py b/conan/mruby/conanfile.py index f1c85b456..32cb2762c 100644 --- a/conan/mruby/conanfile.py +++ b/conan/mruby/conanfile.py @@ -155,7 +155,7 @@ def search_header_path(name) if is_msvc(self): if self.settings.build_type == 'Debug': f.write(" conf.compilers.each { |c| c.flags << '/Od' }\n") - f.write(" conf.compilers.each { |c| c.flags << '/std:c++17' }\n") + f.write(" conf.compilers.each { |c| c.flags << '/std:c++20' }\n") f.write(" conf.compilers.each { |c| c.flags << '/%s' }\n" % msvc_runtime_flag(self)) else: if self.settings.build_type == 'Debug': diff --git a/conan/profiles/xcode b/conan/profiles/xcode index 3121ffdd6..bea2b337a 100644 --- a/conan/profiles/xcode +++ b/conan/profiles/xcode @@ -1,7 +1,7 @@ include(default) [settings] -compiler.cppstd=17 +compiler.cppstd=20 [platform_tool_requires] cmake/3.26.4 diff --git a/conanfile.py b/conanfile.py index 60702ba63..902bc4f0e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -130,7 +130,7 @@ def requirements(self): if self.options.with_ruby: self.requires("mruby/3.2.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) - self.requires("gtest/1.10.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True, test=True) + self.requires("gtest/1.17.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True, test=True) def configure(self): if self.options.shared: From 13a045abf94ebd4804ad315af285c3d2e1f55ade Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 19 Sep 2025 15:05:56 +0200 Subject: [PATCH 02/84] Fixed test issues where operator == rules changed in version 20 --- conan/profiles/clang | 1 + conan/profiles/docker | 1 + conan/profiles/gcc | 1 + conan/profiles/macos | 2 +- conan/profiles/vs32 | 2 +- conan/profiles/vs32debug | 2 +- conan/profiles/vs32shared | 2 +- conan/profiles/vs64 | 2 +- conan/profiles/vs64debug | 2 +- conan/profiles/vs64shared | 2 +- conan/profiles/vsxp | 2 +- docker/alpine/Dockerfile | 4 ++-- docker/ubuntu/Dockerfile | 9 ++++---- src/mtconnect/agent.cpp | 4 ++-- src/mtconnect/buffer/circular_buffer.hpp | 4 ++-- src/mtconnect/device_model/component.hpp | 7 +++++++ src/mtconnect/entity/entity.hpp | 26 ++++++++++++++++-------- 17 files changed, 46 insertions(+), 27 deletions(-) diff --git a/conan/profiles/clang b/conan/profiles/clang index d80d9b6cb..7a37344ec 100644 --- a/conan/profiles/clang +++ b/conan/profiles/clang @@ -2,6 +2,7 @@ include(default) [settings] compiler.libcxx=libstdc++11 +compiler.cppstd=20 [env] CC=/usr/bin/clang diff --git a/conan/profiles/docker b/conan/profiles/docker index 2759a86de..9a48de725 100644 --- a/conan/profiles/docker +++ b/conan/profiles/docker @@ -2,6 +2,7 @@ include(default) [settings] compiler.libcxx=libstdc++11 +compiler.cppstd=20 [options] without_ipv6=True diff --git a/conan/profiles/gcc b/conan/profiles/gcc index 698cc39b2..cac1ad8f7 100644 --- a/conan/profiles/gcc +++ b/conan/profiles/gcc @@ -2,6 +2,7 @@ include(default) [settings] compiler.libcxx=libstdc++11 +compiler.cppstd=20 [system_tools] cmake/>3.23.0 \ No newline at end of file diff --git a/conan/profiles/macos b/conan/profiles/macos index 822d173c7..b7c3df9ac 100644 --- a/conan/profiles/macos +++ b/conan/profiles/macos @@ -2,7 +2,7 @@ include(default) [settings] compiler=apple-clang -compiler.cppstd=gnu17 +compiler.cppstd=20 [system_tools] cmake/>3.26.0 diff --git a/conan/profiles/vs32 b/conan/profiles/vs32 index fb406b017..423cc9523 100644 --- a/conan/profiles/vs32 +++ b/conan/profiles/vs32 @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86 compiler.runtime=static compiler.runtime_type=Release diff --git a/conan/profiles/vs32debug b/conan/profiles/vs32debug index 54344e405..06f2fb2c9 100644 --- a/conan/profiles/vs32debug +++ b/conan/profiles/vs32debug @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86 compiler.runtime=static compiler.runtime_type=Debug diff --git a/conan/profiles/vs32shared b/conan/profiles/vs32shared index d30b8f54c..ca9b3fa6d 100644 --- a/conan/profiles/vs32shared +++ b/conan/profiles/vs32shared @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86 compiler.runtime=dynamic compiler.runtime_type=Release diff --git a/conan/profiles/vs64 b/conan/profiles/vs64 index 729464e42..3e639b8cd 100644 --- a/conan/profiles/vs64 +++ b/conan/profiles/vs64 @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86_64 compiler.runtime=static compiler.runtime_type=Release diff --git a/conan/profiles/vs64debug b/conan/profiles/vs64debug index 226a6a3fe..9445c691d 100644 --- a/conan/profiles/vs64debug +++ b/conan/profiles/vs64debug @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86_64 compiler.runtime=static compiler.runtime_type=Debug diff --git a/conan/profiles/vs64shared b/conan/profiles/vs64shared index ebea4cd9a..13548f255 100644 --- a/conan/profiles/vs64shared +++ b/conan/profiles/vs64shared @@ -2,7 +2,7 @@ include(default) [settings] compiler=msvc -compiler.cppstd=17 +compiler.cppstd=20 arch=x86_64 compiler.runtime=dynamic compiler.runtime_type=Release diff --git a/conan/profiles/vsxp b/conan/profiles/vsxp index 10d64e4a1..1acbe97d5 100644 --- a/conan/profiles/vsxp +++ b/conan/profiles/vsxp @@ -3,7 +3,7 @@ include(default) [settings] compiler=msvc arch=x86 -compiler.cppstd=17 +compiler.cppstd=20 compiler.runtime=static compiler.runtime_type=Release build_type=Release diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index 270e90ae0..ec7599cf2 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -74,7 +74,7 @@ WORKDIR /root/agent COPY . cppagent ARG WITH_TESTS=false -ARG WITH_TESTS_ARG=argument +ARG WITH_TESTS_ARG= # Build and optionally test. Unpack the dist to reduce overhead in the following release step. RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \ @@ -94,7 +94,7 @@ RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \ -o cpack_name=dist \ -o cpack_generator=TGZ \ -pr "$CONAN_PROFILE" \ - "${WITH_TESTS_ARG}" \ + ${WITH_TESTS_ARG} \ && tar xf dist.tar.gz \ && rm dist.tar.gz diff --git a/docker/ubuntu/Dockerfile b/docker/ubuntu/Dockerfile index 7372a5466..a01755a12 100644 --- a/docker/ubuntu/Dockerfile +++ b/docker/ubuntu/Dockerfile @@ -46,13 +46,12 @@ ARG CONAN_CPU_COUNT=2 ARG WITH_RUBY='True' # set some variables -ENV PATH="$HOME/venv3.9/bin:$PATH" ENV CONAN_PROFILE='/root/agent/cppagent/conan/profiles/docker' # update os and add dependencies # note: Dockerfiles run as root by default, so don't need sudo -RUN apt-get update \ - && apt-get install -y \ +RUN apt clean && apt update \ + && apt install -y \ autoconf \ automake \ build-essential \ @@ -72,7 +71,7 @@ WORKDIR /root/agent COPY . cppagent ARG WITH_TESTS=false -ARG WITH_TESTS_ARG=argument +ARG WITH_TESTS_ARG= # Build and optionally test RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \ @@ -91,7 +90,7 @@ RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \ -o cpack_name=dist \ -o cpack_generator=TGZ \ -pr "$CONAN_PROFILE" \ - "${WITH_TESTS_ARG}" \ + ${WITH_TESTS_ARG} \ && tar xf dist.tar.gz \ && rm dist.tar.gz diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index bac1512c9..86ea5d26b 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -477,7 +477,7 @@ namespace mtconnect { return; } - auto callback = [=](config::AsyncContext &context) { + auto callback = [=,this](config::AsyncContext &context) { try { bool changed = false; @@ -604,7 +604,7 @@ namespace mtconnect { createUniqueIds(device); LOG(info) << "Checking if device " << *uuid << " has changed"; - if (*device != *oldDev) + if (device->different(*oldDev)) { LOG(info) << "Device " << *uuid << " changed, updating model"; diff --git a/src/mtconnect/buffer/circular_buffer.hpp b/src/mtconnect/buffer/circular_buffer.hpp index eb3b3f4d6..327aed180 100644 --- a/src/mtconnect/buffer/circular_buffer.hpp +++ b/src/mtconnect/buffer/circular_buffer.hpp @@ -326,8 +326,8 @@ namespace mtconnect::buffer { mutable std::recursive_mutex m_sequenceLock; // Sequence number - volatile SequenceNumber_t m_sequence; - volatile SequenceNumber_t m_firstSequence; + SequenceNumber_t m_sequence; + SequenceNumber_t m_firstSequence; // The sliding/circular buffer to hold all of the events/sample data unsigned int m_slidingBufferSize; diff --git a/src/mtconnect/device_model/component.hpp b/src/mtconnect/device_model/component.hpp index 911b7a5ad..d895a4044 100644 --- a/src/mtconnect/device_model/component.hpp +++ b/src/mtconnect/device_model/component.hpp @@ -219,7 +219,14 @@ namespace mtconnect { /// @return the data item list auto getDataItems() const { return getList("DataItems"); } + /// @brief compares the ids of the component for sorting + /// @param comp other component to compare against + /// @return `true` if this id is less than the comp id bool operator<(const Component &comp) const { return m_id < comp.getId(); } + + /// @brief compares the ids for equality + /// @param comp other component to compare against + /// @return `true` if this id is equal than the comp id bool operator==(const Component &comp) const { return m_id == comp.getId(); } /// @brief connected references by looking them up in the device diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index aa6ac8a19..027652670 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -368,16 +368,26 @@ namespace mtconnect { else return empty; } - + + /// @brief checks if two entity models are different–does a deep analysis + /// @param other the other entity to check + /// @return `true` if the entities are different + bool different(const Entity &other) const; + + /// @brief cover method for entity comparison + /// @param other the other entity to check + /// @return `true` if the entities are different + bool different(const std::shared_ptr other) const { return different(*other); } + /// @brief compare two entities for equality /// @param other the other entity /// @return `true` if they have equal name and properties - bool operator==(const Entity &other) const; + bool operator==(const Entity &other) const { return !different(other); } /// @brief compare two entities for inequality /// @param other the other entity /// @return `true` if they have unequal name and properties - bool operator!=(const Entity &other) const { return !(*this == other); } + bool operator!=(const Entity &other) const { return different(other); } /// @brief update this entity to be the same as other /// @param other the other entity @@ -517,23 +527,23 @@ namespace mtconnect { inline bool operator!=(const Value &v1, const Value &v2) { return !(v1 == v2); } - inline bool Entity::operator==(const Entity &other) const + inline bool Entity::different(const Entity &other) const { if (m_name != other.m_name) - return false; + return true; if (m_properties.size() != other.m_properties.size()) - return false; + return true; for (auto it1 = m_properties.cbegin(), it2 = other.m_properties.cbegin(); it1 != m_properties.cend(); it1++, it2++) { if (it1->first != it2->first || it1->second != it2->second) { - return false; + return true; } } - return true; + return false; } /// @brief variant visitor to merge two entities From 2234ee7e6f695409b321564f4d0ca8fe6ad69913 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 20 Sep 2025 21:18:45 +0200 Subject: [PATCH 03/84] Windows changes for C++ 20 --- src/mtconnect/configuration/service.cpp | 10 +++++----- src/mtconnect/pipeline/timestamp_extractor.hpp | 2 +- src/mtconnect/utilities.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index a3ef8c0dd..ebce29fbb 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -220,7 +220,7 @@ namespace mtconnect { g_service = this; m_isService = true; - SERVICE_TABLE_ENTRY DispatchTable[] = {{"", (LPSERVICE_MAIN_FUNCTION)SvcMain}, + SERVICE_TABLE_ENTRY DispatchTable[] = {{LPSTR(""), (LPSERVICE_MAIN_FUNCTION)SvcMain}, {nullptr, nullptr}}; if (StartServiceCtrlDispatcher(DispatchTable) == 0) @@ -229,7 +229,7 @@ namespace mtconnect { } else { - SvcReportEvent("StartServiceCtrlDispatcher"); + SvcReportEvent(LPSTR("StartServiceCtrlDispatcher")); } } catch (std::exception &e) @@ -516,7 +516,7 @@ namespace mtconnect { if (!g_svcStatusHandle) { - SvcReportEvent("RegisterServiceCtrlHandler"); + SvcReportEvent(LPSTR("RegisterServiceCtrlHandler")); return; } @@ -554,7 +554,7 @@ namespace mtconnect { auto res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0ul, KEY_READ, &agent); if (res != ERROR_SUCCESS) { - SvcReportEvent("RegOpenKey: Could not open MTConnect Agent Key"); + SvcReportEvent(LPSTR("RegOpenKey: Could not open MTConnect Agent Key")); ReportSvcStatus(SERVICE_STOPPED, 1ul, 0ul); return; } @@ -566,7 +566,7 @@ namespace mtconnect { agent = nullptr; if (res != ERROR_SUCCESS) { - SvcReportEvent("RegOpenKey: Could not open ConfigurationFile"); + SvcReportEvent(LPSTR("RegOpenKey: Could not open ConfigurationFile")); ReportSvcStatus(SERVICE_STOPPED, 1ul, 0ul); return; } diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 2cddc792e..6cb163071 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -104,7 +104,7 @@ namespace mtconnect::pipeline { if (has_t) { istringstream in(timestamp.data()); - in >> std::setw(6) >> parse("%FT%T", result); + in >> std::setw(6) >> std::chrono::parse("%FT%T", result); if (!in.good()) { result = now(); diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index da603aa7b..93b49f2f4 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -764,7 +764,7 @@ namespace mtconnect { Timestamp ts; std::istringstream in(timestamp); - in >> std::setw(6) >> parse("%FT%T", ts); + in >> std::setw(6) >> std::chrono::parse("%FT%T", ts); if (!in.good()) { ts = std::chrono::system_clock::now(); From ebe752b35ab80a02dce26fd9e6609e4873558ce9 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 21 Sep 2025 00:34:59 +0200 Subject: [PATCH 04/84] cleaned up tz handling --- .../pipeline/timestamp_extractor.hpp | 2 +- src/mtconnect/utilities.hpp | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 6cb163071..4226971dd 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -104,7 +104,7 @@ namespace mtconnect::pipeline { if (has_t) { istringstream in(timestamp.data()); - in >> std::setw(6) >> std::chrono::parse("%FT%T", result); + in >> std::setw(6) >> date::parse("%FT%T", result); if (!in.good()) { result = now(); diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 93b49f2f4..07b46e6d3 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -228,12 +229,11 @@ namespace mtconnect { case GMT_UV_SEC: return date::format(ISO_8601_FMT, date::floor(timePoint)); case LOCAL: - auto time = system_clock::to_time_t(timePoint); - struct tm timeinfo = {0}; - mt_localtime(&time, &timeinfo); - char timestamp[64] = {0}; - strftime(timestamp, 50u, "%Y-%m-%dT%H:%M:%S%z", &timeinfo); - return timestamp; + { + auto zone = date::current_zone(); + auto zt = date::zoned_time(zone, timePoint); + return date::format("%Y-%m-%dT%H:%M:%S%z", zt); + } } return ""; @@ -757,14 +757,9 @@ namespace mtconnect { /// @return converted `Timestamp` inline Timestamp parseTimestamp(const std::string ×tamp) { - using namespace date; - using namespace std::chrono; - using namespace std::chrono_literals; - using namespace date::literals; - Timestamp ts; std::istringstream in(timestamp); - in >> std::setw(6) >> std::chrono::parse("%FT%T", ts); + in >> std::setw(6) >> date::parse("%FT%T", ts); if (!in.good()) { ts = std::chrono::system_clock::now(); From 562d1c2c50c112f819c9743bd18689b8da3a0ffe Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 21 Sep 2025 22:08:10 +0200 Subject: [PATCH 05/84] formatted --- src/mtconnect/agent.cpp | 2 +- src/mtconnect/entity/entity.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 86ea5d26b..2149853d8 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -477,7 +477,7 @@ namespace mtconnect { return; } - auto callback = [=,this](config::AsyncContext &context) { + auto callback = [=, this](config::AsyncContext &context) { try { bool changed = false; diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index 027652670..e687f12c8 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -368,17 +368,17 @@ namespace mtconnect { else return empty; } - + /// @brief checks if two entity models are different–does a deep analysis /// @param other the other entity to check /// @return `true` if the entities are different bool different(const Entity &other) const; - + /// @brief cover method for entity comparison /// @param other the other entity to check /// @return `true` if the entities are different bool different(const std::shared_ptr other) const { return different(*other); } - + /// @brief compare two entities for equality /// @param other the other entity /// @return `true` if they have equal name and properties From e1bd649bf3ed0fdb254b430eb4d1afd2c6f77f4a Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 21 Sep 2025 22:38:34 +0200 Subject: [PATCH 06/84] Changed globals test to utilities test --- .../pipeline/timestamp_extractor.hpp | 1 - src/mtconnect/ruby/embedded.cpp | 1 - src/mtconnect/ruby/ruby_transform.hpp | 1 - .../source/adapter/shdr/shdr_adapter.cpp | 1 - test_package/CMakeLists.txt | 2 +- test_package/change_observer_test.cpp | 1 - test_package/mtconnect_xml_transform_test.cpp | 1 - test_package/shdr_tokenizer_test.cpp | 1 - test_package/timestamp_extractor_test.cpp | 1 - .../{globals_test.cpp => utilities_test.cpp} | 24 +++++++++---------- 10 files changed, 13 insertions(+), 21 deletions(-) rename test_package/{globals_test.cpp => utilities_test.cpp} (92%) diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 4226971dd..0ce980aaa 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -87,7 +87,6 @@ namespace mtconnect::pipeline { using namespace date; using namespace chrono; using namespace chrono_literals; - using namespace date::literals; using namespace date; // Extract duration diff --git a/src/mtconnect/ruby/embedded.cpp b/src/mtconnect/ruby/embedded.cpp index 4942b6885..29b45b2c6 100644 --- a/src/mtconnect/ruby/embedded.cpp +++ b/src/mtconnect/ruby/embedded.cpp @@ -62,7 +62,6 @@ using namespace std; namespace mtconnect::ruby { using namespace mtconnect::pipeline; using namespace std::literals; - using namespace date::literals; using namespace observation; RClass *RubyObservation::m_eventClass; diff --git a/src/mtconnect/ruby/ruby_transform.hpp b/src/mtconnect/ruby/ruby_transform.hpp index 94693b29d..f1e9800e4 100644 --- a/src/mtconnect/ruby/ruby_transform.hpp +++ b/src/mtconnect/ruby/ruby_transform.hpp @@ -28,7 +28,6 @@ namespace mtconnect::ruby { using namespace mtconnect::pipeline; using namespace std::literals; - using namespace date::literals; using namespace entity; using namespace observation; diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp index 366b4e9b1..695dc247c 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp @@ -34,7 +34,6 @@ using namespace std; using namespace std::literals; -using namespace date::literals; namespace mtconnect::source::adapter::shdr { // Adapter public methods diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 05f82d637..2cd035cc7 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -285,7 +285,7 @@ add_agent_test(correct_timestamp TRUE pipeline) add_agent_test(agent TRUE core) add_agent_test(agent_asset TRUE core) -add_agent_test(globals FALSE core) +add_agent_test(utilities FALSE core) add_agent_test(config_parser FALSE configuration) add_agent_test(config FALSE configuration) diff --git a/test_package/change_observer_test.cpp b/test_package/change_observer_test.cpp index b7501a6c8..d809fec6f 100644 --- a/test_package/change_observer_test.cpp +++ b/test_package/change_observer_test.cpp @@ -31,7 +31,6 @@ using namespace std::chrono_literals; using namespace std; using namespace std::literals; -using namespace date::literals; using WorkGuard = boost::asio::executor_work_guard; diff --git a/test_package/mtconnect_xml_transform_test.cpp b/test_package/mtconnect_xml_transform_test.cpp index 21e3b010e..2c32d004c 100644 --- a/test_package/mtconnect_xml_transform_test.cpp +++ b/test_package/mtconnect_xml_transform_test.cpp @@ -31,7 +31,6 @@ using namespace mtconnect::pipeline; using namespace mtconnect::observation; using namespace mtconnect::asset; using namespace std; -using namespace date::literals; using namespace std::literals; // main diff --git a/test_package/shdr_tokenizer_test.cpp b/test_package/shdr_tokenizer_test.cpp index 76b754d43..ecbea97e8 100644 --- a/test_package/shdr_tokenizer_test.cpp +++ b/test_package/shdr_tokenizer_test.cpp @@ -29,7 +29,6 @@ using namespace mtconnect; using namespace mtconnect::pipeline; using namespace mtconnect::observation; using namespace std; -using namespace date::literals; using namespace std::literals; // main diff --git a/test_package/timestamp_extractor_test.cpp b/test_package/timestamp_extractor_test.cpp index dd355f17a..9c9e2a172 100644 --- a/test_package/timestamp_extractor_test.cpp +++ b/test_package/timestamp_extractor_test.cpp @@ -30,7 +30,6 @@ using namespace mtconnect::entity; using namespace std; using namespace std::literals; using namespace date; -using namespace date::literals; // main int main(int argc, char *argv[]) diff --git a/test_package/globals_test.cpp b/test_package/utilities_test.cpp similarity index 92% rename from test_package/globals_test.cpp rename to test_package/utilities_test.cpp index 48cdee99a..de43e88f4 100644 --- a/test_package/globals_test.cpp +++ b/test_package/utilities_test.cpp @@ -34,7 +34,7 @@ int main(int argc, char *argv[]) return RUN_ALL_TESTS(); } -TEST(GlobalsTest, IntToString) +TEST(UtilitiesTest, IntToString) { ASSERT_EQ((string) "1234", to_string(1234)); ASSERT_EQ((string) "0", to_string(0)); @@ -42,7 +42,7 @@ TEST(GlobalsTest, IntToString) ASSERT_EQ((string) "1", to_string(1)); } -TEST(GlobalsTest, FloatToString) +TEST(UtilitiesTest, FloatToString) { ASSERT_EQ((string) "1.234", format(1.234)); ASSERT_EQ((string) "0", format(0.0)); @@ -50,7 +50,7 @@ TEST(GlobalsTest, FloatToString) ASSERT_EQ((string) "1", format(1.0)); } -TEST(GlobalsTest, ToUpperCase) +TEST(UtilitiesTest, ToUpperCase) { string lower = "abcDef"; ASSERT_EQ((string) "ABCDEF", toUpperCase(lower)); @@ -62,7 +62,7 @@ TEST(GlobalsTest, ToUpperCase) ASSERT_EQ((string) "QWERTY.ASDF|", toUpperCase(lower)); } -TEST(GlobalsTest, IsNonNegativeInteger) +TEST(UtilitiesTest, IsNonNegativeInteger) { ASSERT_TRUE(isNonNegativeInteger("12345")); ASSERT_TRUE(isNonNegativeInteger("123456789012345678901234567890")); @@ -72,7 +72,7 @@ TEST(GlobalsTest, IsNonNegativeInteger) ASSERT_TRUE(!isNonNegativeInteger("123.45")); } -TEST(GlobalsTest, Time) +TEST(UtilitiesTest, Time) { auto time1 = getCurrentTime(GMT); auto time2 = getCurrentTime(GMT); @@ -99,7 +99,7 @@ TEST(GlobalsTest, Time) ASSERT_TRUE(time7 < time9); } -TEST(GlobalsTest, IllegalCharacters) +TEST(UtilitiesTest, IllegalCharacters) { string before1("Don't Change Me"), after1("Don't Change Me"); replaceIllegalCharacters(before1); @@ -114,7 +114,7 @@ TEST(GlobalsTest, IllegalCharacters) ASSERT_EQ(before3, after3); } -TEST(GlobalsTest, GetCurrentTime) +TEST(UtilitiesTest, GetCurrentTime) { auto gmt = getCurrentTime(GMT); auto time = parseTimeMicro(gmt); @@ -140,7 +140,7 @@ TEST(GlobalsTest, GetCurrentTime) ASSERT_EQ(8, n); } -TEST(GlobalsTest, GetCurrentTime2) +TEST(UtilitiesTest, GetCurrentTime2) { // Build a known system time point auto knownTimePoint = std::chrono::system_clock::from_time_t(0); // 1 Jan 1970 00:00:00 @@ -173,14 +173,14 @@ TEST(GlobalsTest, GetCurrentTime2) ASSERT_EQ(string("Thu, 01 Jan 1970 00:00:10 GMT"), humRead); } -TEST(GlobalsTest, ParseTimeMicro) +TEST(UtilitiesTest, ParseTimeMicro) { // This time is 123456 microseconds after the epoch auto v = parseTimeMicro("1970-01-01T00:00:00.123456Z"); ASSERT_EQ(uint64_t {123456}, v); } -TEST(GlobalsTest, AddNamespace) +TEST(UtilitiesTest, AddNamespace) { auto result = addNamespace("//Device//Foo", "m"); ASSERT_EQ(string("//m:Device//m:Foo"), result); @@ -201,7 +201,7 @@ TEST(GlobalsTest, AddNamespace) ASSERT_EQ(string("//m:Device/m:DataItems/"), result); } -TEST(GlobalsTest, ParseTimeMilli) +TEST(UtilitiesTest, ParseTimeMilli) { string v = "2012-11-20T12:33:22.123456"; @@ -213,4 +213,4 @@ TEST(GlobalsTest, ParseTimeMilli) ASSERT_TRUE(1353414802123000LL == time); } -TEST(GlobalsTest, Int64ToString) { ASSERT_EQ((string) "8805345009", to_string(8805345009ULL)); } +TEST(UtilitiesTest, Int64ToString) { ASSERT_EQ((string) "8805345009", to_string(8805345009ULL)); } From fe86af04961dd537476572886c1e875673a5e392 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 21 Sep 2025 22:56:16 +0200 Subject: [PATCH 07/84] Changed copyright from 2009 to 2025 --- agent/cppagent.cpp | 2 +- src/mtconnect/agent.cpp | 2 +- src/mtconnect/agent.hpp | 2 +- src/mtconnect/asset/asset.cpp | 2 +- src/mtconnect/asset/asset.hpp | 2 +- src/mtconnect/asset/asset_buffer.hpp | 2 +- src/mtconnect/asset/asset_storage.hpp | 2 +- src/mtconnect/asset/component_configuration_parameters.cpp | 2 +- src/mtconnect/asset/component_configuration_parameters.hpp | 2 +- src/mtconnect/asset/cutting_tool.cpp | 2 +- src/mtconnect/asset/cutting_tool.hpp | 2 +- src/mtconnect/asset/file_asset.cpp | 2 +- src/mtconnect/asset/file_asset.hpp | 2 +- src/mtconnect/asset/fixture.cpp | 2 +- src/mtconnect/asset/fixture.hpp | 2 +- src/mtconnect/asset/pallet.cpp | 2 +- src/mtconnect/asset/pallet.hpp | 2 +- src/mtconnect/asset/physical_asset.cpp | 2 +- src/mtconnect/asset/physical_asset.hpp | 2 +- src/mtconnect/asset/qif_document.cpp | 2 +- src/mtconnect/asset/qif_document.hpp | 2 +- src/mtconnect/asset/raw_material.cpp | 2 +- src/mtconnect/asset/raw_material.hpp | 2 +- src/mtconnect/buffer/checkpoint.cpp | 2 +- src/mtconnect/buffer/checkpoint.hpp | 2 +- src/mtconnect/buffer/circular_buffer.hpp | 2 +- src/mtconnect/config.hpp | 2 +- src/mtconnect/configuration/agent_config.cpp | 2 +- src/mtconnect/configuration/agent_config.hpp | 2 +- src/mtconnect/configuration/async_context.hpp | 2 +- src/mtconnect/configuration/config_options.hpp | 2 +- src/mtconnect/configuration/hook_manager.hpp | 2 +- src/mtconnect/configuration/parser.cpp | 2 +- src/mtconnect/configuration/parser.hpp | 2 +- src/mtconnect/configuration/service.cpp | 2 +- src/mtconnect/configuration/service.hpp | 2 +- src/mtconnect/device_model/agent_device.cpp | 2 +- src/mtconnect/device_model/agent_device.hpp | 2 +- src/mtconnect/device_model/component.cpp | 2 +- src/mtconnect/device_model/component.hpp | 2 +- src/mtconnect/device_model/composition.cpp | 2 +- src/mtconnect/device_model/composition.hpp | 2 +- src/mtconnect/device_model/configuration/configuration.cpp | 2 +- src/mtconnect/device_model/configuration/configuration.hpp | 2 +- src/mtconnect/device_model/configuration/coordinate_systems.cpp | 2 +- src/mtconnect/device_model/configuration/coordinate_systems.hpp | 2 +- src/mtconnect/device_model/configuration/image_file.cpp | 2 +- src/mtconnect/device_model/configuration/image_file.hpp | 2 +- src/mtconnect/device_model/configuration/motion.cpp | 2 +- src/mtconnect/device_model/configuration/motion.hpp | 2 +- src/mtconnect/device_model/configuration/relationships.cpp | 2 +- src/mtconnect/device_model/configuration/relationships.hpp | 2 +- .../device_model/configuration/sensor_configuration.cpp | 2 +- .../device_model/configuration/sensor_configuration.hpp | 2 +- src/mtconnect/device_model/configuration/solid_model.cpp | 2 +- src/mtconnect/device_model/configuration/solid_model.hpp | 2 +- src/mtconnect/device_model/configuration/specifications.cpp | 2 +- src/mtconnect/device_model/configuration/specifications.hpp | 2 +- src/mtconnect/device_model/data_item/constraints.hpp | 2 +- src/mtconnect/device_model/data_item/data_item.cpp | 2 +- src/mtconnect/device_model/data_item/data_item.hpp | 2 +- src/mtconnect/device_model/data_item/definition.hpp | 2 +- src/mtconnect/device_model/data_item/filter.hpp | 2 +- src/mtconnect/device_model/data_item/relationships.hpp | 2 +- src/mtconnect/device_model/data_item/source.hpp | 2 +- src/mtconnect/device_model/data_item/unit_conversion.cpp | 2 +- src/mtconnect/device_model/data_item/unit_conversion.hpp | 2 +- src/mtconnect/device_model/description.cpp | 2 +- src/mtconnect/device_model/description.hpp | 2 +- src/mtconnect/device_model/device.cpp | 2 +- src/mtconnect/device_model/device.hpp | 2 +- src/mtconnect/device_model/reference.cpp | 2 +- src/mtconnect/device_model/reference.hpp | 2 +- src/mtconnect/entity/data_set.cpp | 2 +- src/mtconnect/entity/data_set.hpp | 2 +- src/mtconnect/entity/entity.cpp | 2 +- src/mtconnect/entity/entity.hpp | 2 +- src/mtconnect/entity/factory.cpp | 2 +- src/mtconnect/entity/factory.hpp | 2 +- src/mtconnect/entity/json_parser.cpp | 2 +- src/mtconnect/entity/json_parser.hpp | 2 +- src/mtconnect/entity/json_printer.hpp | 2 +- src/mtconnect/entity/qname.hpp | 2 +- src/mtconnect/entity/requirement.cpp | 2 +- src/mtconnect/entity/requirement.hpp | 2 +- src/mtconnect/entity/xml_parser.cpp | 2 +- src/mtconnect/entity/xml_parser.hpp | 2 +- src/mtconnect/entity/xml_printer.cpp | 2 +- src/mtconnect/entity/xml_printer.hpp | 2 +- src/mtconnect/logging.hpp | 2 +- src/mtconnect/mqtt/mqtt_authorization.hpp | 2 +- src/mtconnect/mqtt/mqtt_client.hpp | 2 +- src/mtconnect/mqtt/mqtt_client_impl.hpp | 2 +- src/mtconnect/mqtt/mqtt_server.hpp | 2 +- src/mtconnect/mqtt/mqtt_server_impl.hpp | 2 +- src/mtconnect/observation/change_observer.cpp | 2 +- src/mtconnect/observation/change_observer.hpp | 2 +- src/mtconnect/observation/observation.cpp | 2 +- src/mtconnect/observation/observation.hpp | 2 +- src/mtconnect/parser/xml_parser.cpp | 2 +- src/mtconnect/parser/xml_parser.hpp | 2 +- src/mtconnect/pipeline/convert_sample.hpp | 2 +- src/mtconnect/pipeline/correct_timestamp.hpp | 2 +- src/mtconnect/pipeline/deliver.cpp | 2 +- src/mtconnect/pipeline/deliver.hpp | 2 +- src/mtconnect/pipeline/delta_filter.hpp | 2 +- src/mtconnect/pipeline/duplicate_filter.hpp | 2 +- src/mtconnect/pipeline/guard.hpp | 2 +- src/mtconnect/pipeline/json_mapper.cpp | 2 +- src/mtconnect/pipeline/json_mapper.hpp | 2 +- src/mtconnect/pipeline/message_mapper.hpp | 2 +- src/mtconnect/pipeline/mtconnect_xml_transform.hpp | 2 +- src/mtconnect/pipeline/period_filter.hpp | 2 +- src/mtconnect/pipeline/pipeline.hpp | 2 +- src/mtconnect/pipeline/pipeline_context.hpp | 2 +- src/mtconnect/pipeline/pipeline_contract.hpp | 2 +- src/mtconnect/pipeline/response_document.cpp | 2 +- src/mtconnect/pipeline/response_document.hpp | 2 +- src/mtconnect/pipeline/shdr_token_mapper.cpp | 2 +- src/mtconnect/pipeline/shdr_token_mapper.hpp | 2 +- src/mtconnect/pipeline/shdr_tokenizer.hpp | 2 +- src/mtconnect/pipeline/timestamp_extractor.hpp | 2 +- src/mtconnect/pipeline/topic_mapper.hpp | 2 +- src/mtconnect/pipeline/transform.hpp | 2 +- src/mtconnect/pipeline/upcase_value.hpp | 2 +- src/mtconnect/pipeline/validator.hpp | 2 +- src/mtconnect/printer/json_printer.cpp | 2 +- src/mtconnect/printer/json_printer.hpp | 2 +- src/mtconnect/printer/json_printer_helper.hpp | 2 +- src/mtconnect/printer/printer.hpp | 2 +- src/mtconnect/printer/xml_helper.hpp | 2 +- src/mtconnect/printer/xml_printer.cpp | 2 +- src/mtconnect/printer/xml_printer.hpp | 2 +- src/mtconnect/printer/xml_printer_helper.hpp | 2 +- src/mtconnect/ruby/embedded.cpp | 2 +- src/mtconnect/ruby/embedded.hpp | 2 +- src/mtconnect/ruby/ruby_agent.hpp | 2 +- src/mtconnect/ruby/ruby_entity.hpp | 2 +- src/mtconnect/ruby/ruby_observation.hpp | 2 +- src/mtconnect/ruby/ruby_pipeline.hpp | 2 +- src/mtconnect/ruby/ruby_smart_ptr.hpp | 2 +- src/mtconnect/ruby/ruby_transform.hpp | 2 +- src/mtconnect/ruby/ruby_type.hpp | 2 +- src/mtconnect/ruby/ruby_vm.hpp | 2 +- src/mtconnect/sink/rest_sink/cached_file.hpp | 2 +- src/mtconnect/sink/rest_sink/error.cpp | 2 +- src/mtconnect/sink/rest_sink/error.hpp | 2 +- src/mtconnect/sink/rest_sink/file_cache.cpp | 2 +- src/mtconnect/sink/rest_sink/file_cache.hpp | 2 +- src/mtconnect/sink/rest_sink/parameter.hpp | 2 +- src/mtconnect/sink/rest_sink/request.hpp | 2 +- src/mtconnect/sink/rest_sink/response.hpp | 2 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 2 +- src/mtconnect/sink/rest_sink/rest_service.hpp | 2 +- src/mtconnect/sink/rest_sink/routing.hpp | 2 +- src/mtconnect/sink/rest_sink/server.cpp | 2 +- src/mtconnect/sink/rest_sink/server.hpp | 2 +- src/mtconnect/sink/rest_sink/session.hpp | 2 +- src/mtconnect/sink/rest_sink/session_impl.cpp | 2 +- src/mtconnect/sink/rest_sink/session_impl.hpp | 2 +- src/mtconnect/sink/rest_sink/tls_dector.hpp | 2 +- src/mtconnect/sink/sink.cpp | 2 +- src/mtconnect/sink/sink.hpp | 2 +- src/mtconnect/source/adapter/adapter.hpp | 2 +- src/mtconnect/source/adapter/adapter_pipeline.cpp | 2 +- src/mtconnect/source/adapter/adapter_pipeline.hpp | 2 +- src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp | 2 +- src/mtconnect/source/adapter/agent_adapter/agent_adapter.hpp | 2 +- src/mtconnect/source/adapter/agent_adapter/http_session.hpp | 2 +- src/mtconnect/source/adapter/agent_adapter/https_session.hpp | 2 +- src/mtconnect/source/adapter/agent_adapter/session.hpp | 2 +- src/mtconnect/source/adapter/agent_adapter/session_impl.hpp | 2 +- src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp | 2 +- src/mtconnect/source/adapter/mqtt/mqtt_adapter.hpp | 2 +- src/mtconnect/source/adapter/shdr/connector.cpp | 2 +- src/mtconnect/source/adapter/shdr/connector.hpp | 2 +- src/mtconnect/source/adapter/shdr/shdr_adapter.cpp | 2 +- src/mtconnect/source/adapter/shdr/shdr_adapter.hpp | 2 +- src/mtconnect/source/adapter/shdr/shdr_pipeline.cpp | 2 +- src/mtconnect/source/adapter/shdr/shdr_pipeline.hpp | 2 +- src/mtconnect/source/error_code.hpp | 2 +- src/mtconnect/source/loopback_source.cpp | 2 +- src/mtconnect/source/loopback_source.hpp | 2 +- src/mtconnect/source/source.cpp | 2 +- src/mtconnect/source/source.hpp | 2 +- src/mtconnect/utilities.cpp | 2 +- src/mtconnect/utilities.hpp | 2 +- src/mtconnect/validation/observations.cpp | 2 +- src/mtconnect/validation/observations.hpp | 2 +- src/mtconnect/version.cpp | 2 +- test_package/adapter_test.cpp | 2 +- test_package/agent_adapter_test.cpp | 2 +- test_package/agent_asset_test.cpp | 2 +- test_package/agent_device_test.cpp | 2 +- test_package/agent_test.cpp | 2 +- test_package/agent_test_helper.cpp | 2 +- test_package/agent_test_helper.hpp | 2 +- test_package/asset_buffer_test.cpp | 2 +- test_package/asset_hash_test.cpp | 2 +- test_package/asset_test.cpp | 2 +- test_package/change_observer_test.cpp | 2 +- test_package/checkpoint_test.cpp | 2 +- test_package/circular_buffer_test.cpp | 2 +- test_package/component_parameters_test.cpp | 2 +- test_package/component_test.cpp | 2 +- test_package/composition_test.cpp | 2 +- test_package/config_parser_test.cpp | 2 +- test_package/config_test.cpp | 2 +- test_package/connector_test.cpp | 2 +- test_package/coordinate_system_test.cpp | 2 +- test_package/correct_timestamp_test.cpp | 2 +- test_package/cutting_tool_test.cpp | 2 +- test_package/data_item_mapping_test.cpp | 2 +- test_package/data_item_test.cpp | 2 +- test_package/data_set_test.cpp | 2 +- test_package/device_test.cpp | 2 +- test_package/duplicate_filter_test.cpp | 2 +- test_package/embedded_ruby_test.cpp | 2 +- test_package/entity_parser_test.cpp | 2 +- test_package/entity_printer_test.cpp | 2 +- test_package/entity_test.cpp | 2 +- test_package/file_asset_test.cpp | 2 +- test_package/file_cache_test.cpp | 2 +- test_package/fixture_test.cpp | 2 +- test_package/http_server_test.cpp | 2 +- test_package/image_file_test.cpp | 2 +- test_package/json_helper.hpp | 2 +- test_package/json_mapping_test.cpp | 2 +- test_package/json_parser_test.cpp | 2 +- test_package/json_printer_asset_test.cpp | 2 +- test_package/json_printer_error_test.cpp | 2 +- test_package/json_printer_probe_test.cpp | 2 +- test_package/json_printer_stream_test.cpp | 2 +- test_package/json_printer_test.cpp | 2 +- test_package/kinematics_test.cpp | 2 +- test_package/mqtt_adapter_test.cpp | 2 +- test_package/mqtt_isolated_test.cpp | 2 +- test_package/mqtt_sink_test.cpp | 2 +- test_package/mtconnect_xml_transform_test.cpp | 2 +- test_package/observation_test.cpp | 2 +- test_package/observation_validation_test.cpp | 2 +- test_package/pallet_test.cpp | 2 +- test_package/period_filter_test.cpp | 2 +- test_package/physical_asset_test.cpp | 2 +- test_package/pipeline_deliver_test.cpp | 2 +- test_package/pipeline_edit_test.cpp | 2 +- test_package/qif_document_test.cpp | 2 +- test_package/qname_test.cpp | 2 +- test_package/raw_material_test.cpp | 2 +- test_package/references_test.cpp | 2 +- test_package/relationship_test.cpp | 2 +- test_package/response_document_test.cpp | 2 +- test_package/routing_test.cpp | 2 +- test_package/sensor_configuration_test.cpp | 2 +- test_package/shdr_tokenizer_test.cpp | 2 +- test_package/solid_model_test.cpp | 2 +- test_package/specification_test.cpp | 2 +- test_package/table_test.cpp | 2 +- test_package/test_utilities.hpp | 2 +- test_package/testadapter_service.cpp | 2 +- test_package/testadapter_service.hpp | 2 +- test_package/testsink_service.cpp | 2 +- test_package/testsink_service.hpp | 2 +- test_package/timestamp_extractor_test.cpp | 2 +- test_package/tls_http_server_test.cpp | 2 +- test_package/topic_mapping_test.cpp | 2 +- test_package/unit_conversion_test.cpp | 2 +- test_package/url_parser_test.cpp | 2 +- test_package/utilities_test.cpp | 2 +- test_package/websockets_test.cpp | 2 +- test_package/xml_parser_test.cpp | 2 +- test_package/xml_printer_test.cpp | 2 +- 272 files changed, 272 insertions(+), 272 deletions(-) diff --git a/agent/cppagent.cpp b/agent/cppagent.cpp index 3557e01a4..12f728208 100644 --- a/agent/cppagent.cpp +++ b/agent/cppagent.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 2149853d8..8efbb565c 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp index cd11c975f..79fc144fd 100644 --- a/src/mtconnect/agent.hpp +++ b/src/mtconnect/agent.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/asset.cpp b/src/mtconnect/asset/asset.cpp index ac51d0cd9..92422d010 100644 --- a/src/mtconnect/asset/asset.cpp +++ b/src/mtconnect/asset/asset.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/asset.hpp b/src/mtconnect/asset/asset.hpp index 6d9fdc096..715b31cee 100644 --- a/src/mtconnect/asset/asset.hpp +++ b/src/mtconnect/asset/asset.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/asset_buffer.hpp b/src/mtconnect/asset/asset_buffer.hpp index 662780745..77d8d09cb 100644 --- a/src/mtconnect/asset/asset_buffer.hpp +++ b/src/mtconnect/asset/asset_buffer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/asset_storage.hpp b/src/mtconnect/asset/asset_storage.hpp index ff90e49a4..5b13c2ed3 100644 --- a/src/mtconnect/asset/asset_storage.hpp +++ b/src/mtconnect/asset/asset_storage.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/component_configuration_parameters.cpp b/src/mtconnect/asset/component_configuration_parameters.cpp index d341b6d8c..687a56c74 100644 --- a/src/mtconnect/asset/component_configuration_parameters.cpp +++ b/src/mtconnect/asset/component_configuration_parameters.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/component_configuration_parameters.hpp b/src/mtconnect/asset/component_configuration_parameters.hpp index fc40d3449..7fe5d85bd 100644 --- a/src/mtconnect/asset/component_configuration_parameters.hpp +++ b/src/mtconnect/asset/component_configuration_parameters.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/cutting_tool.cpp b/src/mtconnect/asset/cutting_tool.cpp index 245f0b2a6..3c09a973f 100644 --- a/src/mtconnect/asset/cutting_tool.cpp +++ b/src/mtconnect/asset/cutting_tool.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/cutting_tool.hpp b/src/mtconnect/asset/cutting_tool.hpp index 969a01270..af98b1e97 100644 --- a/src/mtconnect/asset/cutting_tool.hpp +++ b/src/mtconnect/asset/cutting_tool.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/file_asset.cpp b/src/mtconnect/asset/file_asset.cpp index b001c86ca..9e3a30574 100644 --- a/src/mtconnect/asset/file_asset.cpp +++ b/src/mtconnect/asset/file_asset.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/file_asset.hpp b/src/mtconnect/asset/file_asset.hpp index 9c5ea9797..df01b7bc8 100644 --- a/src/mtconnect/asset/file_asset.hpp +++ b/src/mtconnect/asset/file_asset.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/fixture.cpp b/src/mtconnect/asset/fixture.cpp index 16bf509a8..2225f9f4b 100644 --- a/src/mtconnect/asset/fixture.cpp +++ b/src/mtconnect/asset/fixture.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/fixture.hpp b/src/mtconnect/asset/fixture.hpp index 5823f9432..f89b6257d 100644 --- a/src/mtconnect/asset/fixture.hpp +++ b/src/mtconnect/asset/fixture.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/pallet.cpp b/src/mtconnect/asset/pallet.cpp index 1f3023620..dfb84916b 100644 --- a/src/mtconnect/asset/pallet.cpp +++ b/src/mtconnect/asset/pallet.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/pallet.hpp b/src/mtconnect/asset/pallet.hpp index 77894aaf4..bf0a2690f 100644 --- a/src/mtconnect/asset/pallet.hpp +++ b/src/mtconnect/asset/pallet.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/physical_asset.cpp b/src/mtconnect/asset/physical_asset.cpp index c5e413d0b..aa609d018 100644 --- a/src/mtconnect/asset/physical_asset.cpp +++ b/src/mtconnect/asset/physical_asset.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/physical_asset.hpp b/src/mtconnect/asset/physical_asset.hpp index 216bc73ca..05b7ae4eb 100644 --- a/src/mtconnect/asset/physical_asset.hpp +++ b/src/mtconnect/asset/physical_asset.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/qif_document.cpp b/src/mtconnect/asset/qif_document.cpp index 6c762cd12..e227247bd 100644 --- a/src/mtconnect/asset/qif_document.cpp +++ b/src/mtconnect/asset/qif_document.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/qif_document.hpp b/src/mtconnect/asset/qif_document.hpp index 45bc7f2f2..a37b4de02 100644 --- a/src/mtconnect/asset/qif_document.hpp +++ b/src/mtconnect/asset/qif_document.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/raw_material.cpp b/src/mtconnect/asset/raw_material.cpp index 0ca15b37d..58a92f99e 100644 --- a/src/mtconnect/asset/raw_material.cpp +++ b/src/mtconnect/asset/raw_material.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/asset/raw_material.hpp b/src/mtconnect/asset/raw_material.hpp index 76af8d858..8f61ca959 100644 --- a/src/mtconnect/asset/raw_material.hpp +++ b/src/mtconnect/asset/raw_material.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/buffer/checkpoint.cpp b/src/mtconnect/buffer/checkpoint.cpp index fd5ec3034..9e39341d2 100644 --- a/src/mtconnect/buffer/checkpoint.cpp +++ b/src/mtconnect/buffer/checkpoint.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/buffer/checkpoint.hpp b/src/mtconnect/buffer/checkpoint.hpp index b830c223d..48c98ed44 100644 --- a/src/mtconnect/buffer/checkpoint.hpp +++ b/src/mtconnect/buffer/checkpoint.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/buffer/circular_buffer.hpp b/src/mtconnect/buffer/circular_buffer.hpp index 327aed180..3ac0d475a 100644 --- a/src/mtconnect/buffer/circular_buffer.hpp +++ b/src/mtconnect/buffer/circular_buffer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/config.hpp b/src/mtconnect/config.hpp index a522fe123..a4868684e 100644 --- a/src/mtconnect/config.hpp +++ b/src/mtconnect/config.hpp @@ -1,7 +1,7 @@ #pragma once // -// Copyright Copyright 2009-2024, AMT � The Association For Manufacturing Technology (�AMT�) +// Copyright Copyright 2009-2025, AMT � The Association For Manufacturing Technology (�AMT�) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 45cd0b240..2c7a19d9d 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index eb0b51414..72191af28 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp index 910c07ee1..aa92c3e5b 100644 --- a/src/mtconnect/configuration/async_context.hpp +++ b/src/mtconnect/configuration/async_context.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/config_options.hpp b/src/mtconnect/configuration/config_options.hpp index b0ca2e951..b192c70d4 100644 --- a/src/mtconnect/configuration/config_options.hpp +++ b/src/mtconnect/configuration/config_options.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/hook_manager.hpp b/src/mtconnect/configuration/hook_manager.hpp index d831146c8..4988e8277 100644 --- a/src/mtconnect/configuration/hook_manager.hpp +++ b/src/mtconnect/configuration/hook_manager.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/parser.cpp b/src/mtconnect/configuration/parser.cpp index 2baeb8e15..c365e9563 100644 --- a/src/mtconnect/configuration/parser.cpp +++ b/src/mtconnect/configuration/parser.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/parser.hpp b/src/mtconnect/configuration/parser.hpp index 13d9bb0d8..7c7a11c88 100644 --- a/src/mtconnect/configuration/parser.hpp +++ b/src/mtconnect/configuration/parser.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index ebce29fbb..a98185912 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/configuration/service.hpp b/src/mtconnect/configuration/service.hpp index fca63e1e5..7795965be 100644 --- a/src/mtconnect/configuration/service.hpp +++ b/src/mtconnect/configuration/service.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/agent_device.cpp b/src/mtconnect/device_model/agent_device.cpp index 7c6c3fb02..287e4065a 100644 --- a/src/mtconnect/device_model/agent_device.cpp +++ b/src/mtconnect/device_model/agent_device.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/agent_device.hpp b/src/mtconnect/device_model/agent_device.hpp index 4b693b5e6..52aa94ed1 100644 --- a/src/mtconnect/device_model/agent_device.hpp +++ b/src/mtconnect/device_model/agent_device.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/component.cpp b/src/mtconnect/device_model/component.cpp index 779bccc12..ee8bcc030 100644 --- a/src/mtconnect/device_model/component.cpp +++ b/src/mtconnect/device_model/component.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/component.hpp b/src/mtconnect/device_model/component.hpp index d895a4044..6288cb754 100644 --- a/src/mtconnect/device_model/component.hpp +++ b/src/mtconnect/device_model/component.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/composition.cpp b/src/mtconnect/device_model/composition.cpp index 40f57cd2a..d0bd73e68 100644 --- a/src/mtconnect/device_model/composition.cpp +++ b/src/mtconnect/device_model/composition.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/composition.hpp b/src/mtconnect/device_model/composition.hpp index 97bdad4c5..fc9f37955 100644 --- a/src/mtconnect/device_model/composition.hpp +++ b/src/mtconnect/device_model/composition.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/configuration.cpp b/src/mtconnect/device_model/configuration/configuration.cpp index 44cbcaa71..2af947dfb 100644 --- a/src/mtconnect/device_model/configuration/configuration.cpp +++ b/src/mtconnect/device_model/configuration/configuration.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/configuration.hpp b/src/mtconnect/device_model/configuration/configuration.hpp index dc01d1f3f..453eebb20 100644 --- a/src/mtconnect/device_model/configuration/configuration.hpp +++ b/src/mtconnect/device_model/configuration/configuration.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/coordinate_systems.cpp b/src/mtconnect/device_model/configuration/coordinate_systems.cpp index a36b27895..0785dca95 100644 --- a/src/mtconnect/device_model/configuration/coordinate_systems.cpp +++ b/src/mtconnect/device_model/configuration/coordinate_systems.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/coordinate_systems.hpp b/src/mtconnect/device_model/configuration/coordinate_systems.hpp index 8fdc819f3..117cc06e0 100644 --- a/src/mtconnect/device_model/configuration/coordinate_systems.hpp +++ b/src/mtconnect/device_model/configuration/coordinate_systems.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/image_file.cpp b/src/mtconnect/device_model/configuration/image_file.cpp index 84be05aaa..ae9f0d86d 100644 --- a/src/mtconnect/device_model/configuration/image_file.cpp +++ b/src/mtconnect/device_model/configuration/image_file.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/image_file.hpp b/src/mtconnect/device_model/configuration/image_file.hpp index 6aac7f61c..46e67540b 100644 --- a/src/mtconnect/device_model/configuration/image_file.hpp +++ b/src/mtconnect/device_model/configuration/image_file.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/motion.cpp b/src/mtconnect/device_model/configuration/motion.cpp index 1d7d06c34..59ba8ab5c 100644 --- a/src/mtconnect/device_model/configuration/motion.cpp +++ b/src/mtconnect/device_model/configuration/motion.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/motion.hpp b/src/mtconnect/device_model/configuration/motion.hpp index 1594e309d..f5fa9c765 100644 --- a/src/mtconnect/device_model/configuration/motion.hpp +++ b/src/mtconnect/device_model/configuration/motion.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/relationships.cpp b/src/mtconnect/device_model/configuration/relationships.cpp index a2ed6d78f..0ad5a6a1b 100644 --- a/src/mtconnect/device_model/configuration/relationships.cpp +++ b/src/mtconnect/device_model/configuration/relationships.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/relationships.hpp b/src/mtconnect/device_model/configuration/relationships.hpp index c2a6fb462..7bb420410 100644 --- a/src/mtconnect/device_model/configuration/relationships.hpp +++ b/src/mtconnect/device_model/configuration/relationships.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/sensor_configuration.cpp b/src/mtconnect/device_model/configuration/sensor_configuration.cpp index 2bb26ecde..95b507605 100644 --- a/src/mtconnect/device_model/configuration/sensor_configuration.cpp +++ b/src/mtconnect/device_model/configuration/sensor_configuration.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/sensor_configuration.hpp b/src/mtconnect/device_model/configuration/sensor_configuration.hpp index 8eeb25b64..295c946a0 100644 --- a/src/mtconnect/device_model/configuration/sensor_configuration.hpp +++ b/src/mtconnect/device_model/configuration/sensor_configuration.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/solid_model.cpp b/src/mtconnect/device_model/configuration/solid_model.cpp index 6c96236b1..75a969a8a 100644 --- a/src/mtconnect/device_model/configuration/solid_model.cpp +++ b/src/mtconnect/device_model/configuration/solid_model.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/solid_model.hpp b/src/mtconnect/device_model/configuration/solid_model.hpp index fa406d9a5..9ffdbfb68 100644 --- a/src/mtconnect/device_model/configuration/solid_model.hpp +++ b/src/mtconnect/device_model/configuration/solid_model.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/specifications.cpp b/src/mtconnect/device_model/configuration/specifications.cpp index a4d55bc0c..daa49b8b8 100644 --- a/src/mtconnect/device_model/configuration/specifications.cpp +++ b/src/mtconnect/device_model/configuration/specifications.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/configuration/specifications.hpp b/src/mtconnect/device_model/configuration/specifications.hpp index 226af76a1..2b65e5216 100644 --- a/src/mtconnect/device_model/configuration/specifications.hpp +++ b/src/mtconnect/device_model/configuration/specifications.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/constraints.hpp b/src/mtconnect/device_model/data_item/constraints.hpp index 21c362955..da1870f5c 100644 --- a/src/mtconnect/device_model/data_item/constraints.hpp +++ b/src/mtconnect/device_model/data_item/constraints.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/data_item.cpp b/src/mtconnect/device_model/data_item/data_item.cpp index c3007387e..a6828900c 100644 --- a/src/mtconnect/device_model/data_item/data_item.cpp +++ b/src/mtconnect/device_model/data_item/data_item.cpp @@ -1,6 +1,6 @@ // // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/data_item.hpp b/src/mtconnect/device_model/data_item/data_item.hpp index 65f769807..2c41a2385 100644 --- a/src/mtconnect/device_model/data_item/data_item.hpp +++ b/src/mtconnect/device_model/data_item/data_item.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/definition.hpp b/src/mtconnect/device_model/data_item/definition.hpp index f1ba7245b..97715e5a4 100644 --- a/src/mtconnect/device_model/data_item/definition.hpp +++ b/src/mtconnect/device_model/data_item/definition.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/filter.hpp b/src/mtconnect/device_model/data_item/filter.hpp index 779f6d3cc..1337eb1df 100644 --- a/src/mtconnect/device_model/data_item/filter.hpp +++ b/src/mtconnect/device_model/data_item/filter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/relationships.hpp b/src/mtconnect/device_model/data_item/relationships.hpp index 10c34c8c3..78861aa86 100644 --- a/src/mtconnect/device_model/data_item/relationships.hpp +++ b/src/mtconnect/device_model/data_item/relationships.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/source.hpp b/src/mtconnect/device_model/data_item/source.hpp index e0895282d..3b18306a4 100644 --- a/src/mtconnect/device_model/data_item/source.hpp +++ b/src/mtconnect/device_model/data_item/source.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/unit_conversion.cpp b/src/mtconnect/device_model/data_item/unit_conversion.cpp index 78fc8a439..1d052dcf8 100644 --- a/src/mtconnect/device_model/data_item/unit_conversion.cpp +++ b/src/mtconnect/device_model/data_item/unit_conversion.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/data_item/unit_conversion.hpp b/src/mtconnect/device_model/data_item/unit_conversion.hpp index 068a43aa7..3ec8e43ea 100644 --- a/src/mtconnect/device_model/data_item/unit_conversion.hpp +++ b/src/mtconnect/device_model/data_item/unit_conversion.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/description.cpp b/src/mtconnect/device_model/description.cpp index 9a7c1cb4f..57c1dc130 100644 --- a/src/mtconnect/device_model/description.cpp +++ b/src/mtconnect/device_model/description.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/description.hpp b/src/mtconnect/device_model/description.hpp index 8789eba6f..c64216dd5 100644 --- a/src/mtconnect/device_model/description.hpp +++ b/src/mtconnect/device_model/description.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index bb58cb0a1..fea72bc30 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/device.hpp b/src/mtconnect/device_model/device.hpp index 650fe3ecc..7042f069f 100644 --- a/src/mtconnect/device_model/device.hpp +++ b/src/mtconnect/device_model/device.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/reference.cpp b/src/mtconnect/device_model/reference.cpp index bc48b32fa..1d951e805 100644 --- a/src/mtconnect/device_model/reference.cpp +++ b/src/mtconnect/device_model/reference.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/device_model/reference.hpp b/src/mtconnect/device_model/reference.hpp index ba9595a5a..f3a41dc60 100644 --- a/src/mtconnect/device_model/reference.hpp +++ b/src/mtconnect/device_model/reference.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT � The Association For Manufacturing Technology (�AMT�) +// Copyright Copyright 2009-2025, AMT � The Association For Manufacturing Technology (�AMT�) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/data_set.cpp b/src/mtconnect/entity/data_set.cpp index 4358e762c..9548f64a1 100644 --- a/src/mtconnect/entity/data_set.cpp +++ b/src/mtconnect/entity/data_set.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/data_set.hpp b/src/mtconnect/entity/data_set.hpp index d528f402d..e6dfc66c2 100644 --- a/src/mtconnect/entity/data_set.hpp +++ b/src/mtconnect/entity/data_set.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/entity.cpp b/src/mtconnect/entity/entity.cpp index 9a55ca18f..0b8da9a45 100644 --- a/src/mtconnect/entity/entity.cpp +++ b/src/mtconnect/entity/entity.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index e687f12c8..b83bf6352 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/factory.cpp b/src/mtconnect/entity/factory.cpp index e763accdd..eb93aebd5 100644 --- a/src/mtconnect/entity/factory.cpp +++ b/src/mtconnect/entity/factory.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/factory.hpp b/src/mtconnect/entity/factory.hpp index 6da238d40..2f9474721 100644 --- a/src/mtconnect/entity/factory.hpp +++ b/src/mtconnect/entity/factory.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/json_parser.cpp b/src/mtconnect/entity/json_parser.cpp index 3976d2fb6..638924118 100644 --- a/src/mtconnect/entity/json_parser.cpp +++ b/src/mtconnect/entity/json_parser.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT The Association For Manufacturing Technology (AMT) +// Copyright Copyright 2009-2025, AMT The Association For Manufacturing Technology (AMT) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/json_parser.hpp b/src/mtconnect/entity/json_parser.hpp index 2fa29a0bb..0e8b117d1 100644 --- a/src/mtconnect/entity/json_parser.hpp +++ b/src/mtconnect/entity/json_parser.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT � The Association For Manufacturing Technology (�AMT�) +// Copyright Copyright 2009-2025, AMT � The Association For Manufacturing Technology (�AMT�) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/json_printer.hpp b/src/mtconnect/entity/json_printer.hpp index 47e64f648..12cea9ca0 100644 --- a/src/mtconnect/entity/json_printer.hpp +++ b/src/mtconnect/entity/json_printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/qname.hpp b/src/mtconnect/entity/qname.hpp index c2c5b9eda..e97e67954 100644 --- a/src/mtconnect/entity/qname.hpp +++ b/src/mtconnect/entity/qname.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/requirement.cpp b/src/mtconnect/entity/requirement.cpp index 8198a71ba..51c2848d5 100644 --- a/src/mtconnect/entity/requirement.cpp +++ b/src/mtconnect/entity/requirement.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/requirement.hpp b/src/mtconnect/entity/requirement.hpp index 1ed29f0bc..ebacbf824 100644 --- a/src/mtconnect/entity/requirement.hpp +++ b/src/mtconnect/entity/requirement.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/xml_parser.cpp b/src/mtconnect/entity/xml_parser.cpp index 395270874..c509e8995 100644 --- a/src/mtconnect/entity/xml_parser.cpp +++ b/src/mtconnect/entity/xml_parser.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/xml_parser.hpp b/src/mtconnect/entity/xml_parser.hpp index d771dc866..30e76b036 100644 --- a/src/mtconnect/entity/xml_parser.hpp +++ b/src/mtconnect/entity/xml_parser.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp index f12ae1cd0..ce5a4122c 100644 --- a/src/mtconnect/entity/xml_printer.cpp +++ b/src/mtconnect/entity/xml_printer.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/entity/xml_printer.hpp b/src/mtconnect/entity/xml_printer.hpp index 23a8aa5dd..cb378b823 100644 --- a/src/mtconnect/entity/xml_printer.hpp +++ b/src/mtconnect/entity/xml_printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/logging.hpp b/src/mtconnect/logging.hpp index 7f7ac3714..80735efab 100644 --- a/src/mtconnect/logging.hpp +++ b/src/mtconnect/logging.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/mqtt/mqtt_authorization.hpp b/src/mtconnect/mqtt/mqtt_authorization.hpp index 73c79ace2..dabb69c02 100644 --- a/src/mtconnect/mqtt/mqtt_authorization.hpp +++ b/src/mtconnect/mqtt/mqtt_authorization.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/mqtt/mqtt_client.hpp b/src/mtconnect/mqtt/mqtt_client.hpp index 0924f926b..bf5050b91 100644 --- a/src/mtconnect/mqtt/mqtt_client.hpp +++ b/src/mtconnect/mqtt/mqtt_client.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/mqtt/mqtt_client_impl.hpp b/src/mtconnect/mqtt/mqtt_client_impl.hpp index 850387b89..9fb8db361 100644 --- a/src/mtconnect/mqtt/mqtt_client_impl.hpp +++ b/src/mtconnect/mqtt/mqtt_client_impl.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/mqtt/mqtt_server.hpp b/src/mtconnect/mqtt/mqtt_server.hpp index 8d38700a9..0a8f1fcea 100644 --- a/src/mtconnect/mqtt/mqtt_server.hpp +++ b/src/mtconnect/mqtt/mqtt_server.hpp @@ -1,5 +1,5 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/mtconnect/mqtt/mqtt_server_impl.hpp b/src/mtconnect/mqtt/mqtt_server_impl.hpp index 4c3b05715..ce8a9f621 100644 --- a/src/mtconnect/mqtt/mqtt_server_impl.hpp +++ b/src/mtconnect/mqtt/mqtt_server_impl.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/observation/change_observer.cpp b/src/mtconnect/observation/change_observer.cpp index f489b287c..522ec9707 100644 --- a/src/mtconnect/observation/change_observer.cpp +++ b/src/mtconnect/observation/change_observer.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/observation/change_observer.hpp b/src/mtconnect/observation/change_observer.hpp index 5b0dc1ad2..deb6fc950 100644 --- a/src/mtconnect/observation/change_observer.hpp +++ b/src/mtconnect/observation/change_observer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index f2a3609c5..58f276b16 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/observation/observation.hpp b/src/mtconnect/observation/observation.hpp index bc2e48537..9aa52a07c 100644 --- a/src/mtconnect/observation/observation.hpp +++ b/src/mtconnect/observation/observation.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/parser/xml_parser.cpp b/src/mtconnect/parser/xml_parser.cpp index 6485ecfef..fe41b4aa7 100644 --- a/src/mtconnect/parser/xml_parser.cpp +++ b/src/mtconnect/parser/xml_parser.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/parser/xml_parser.hpp b/src/mtconnect/parser/xml_parser.hpp index e5747c987..ce5af0785 100644 --- a/src/mtconnect/parser/xml_parser.hpp +++ b/src/mtconnect/parser/xml_parser.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/convert_sample.hpp b/src/mtconnect/pipeline/convert_sample.hpp index 90dc9e907..a7b775aa1 100644 --- a/src/mtconnect/pipeline/convert_sample.hpp +++ b/src/mtconnect/pipeline/convert_sample.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/correct_timestamp.hpp b/src/mtconnect/pipeline/correct_timestamp.hpp index faddde4c3..8d33c2a04 100644 --- a/src/mtconnect/pipeline/correct_timestamp.hpp +++ b/src/mtconnect/pipeline/correct_timestamp.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/deliver.cpp b/src/mtconnect/pipeline/deliver.cpp index a97af1b20..8b6a96d35 100644 --- a/src/mtconnect/pipeline/deliver.cpp +++ b/src/mtconnect/pipeline/deliver.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/deliver.hpp b/src/mtconnect/pipeline/deliver.hpp index f48b45080..acd583bb4 100644 --- a/src/mtconnect/pipeline/deliver.hpp +++ b/src/mtconnect/pipeline/deliver.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/delta_filter.hpp b/src/mtconnect/pipeline/delta_filter.hpp index 313f92e49..f6e955f89 100644 --- a/src/mtconnect/pipeline/delta_filter.hpp +++ b/src/mtconnect/pipeline/delta_filter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/duplicate_filter.hpp b/src/mtconnect/pipeline/duplicate_filter.hpp index 0493baa3b..981fe9c18 100644 --- a/src/mtconnect/pipeline/duplicate_filter.hpp +++ b/src/mtconnect/pipeline/duplicate_filter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/guard.hpp b/src/mtconnect/pipeline/guard.hpp index 98cb89dca..1ed5a1271 100644 --- a/src/mtconnect/pipeline/guard.hpp +++ b/src/mtconnect/pipeline/guard.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/json_mapper.cpp b/src/mtconnect/pipeline/json_mapper.cpp index 6f4a072c4..127adcbe2 100644 --- a/src/mtconnect/pipeline/json_mapper.cpp +++ b/src/mtconnect/pipeline/json_mapper.cpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/json_mapper.hpp b/src/mtconnect/pipeline/json_mapper.hpp index 34835ecf0..7c90301ce 100644 --- a/src/mtconnect/pipeline/json_mapper.hpp +++ b/src/mtconnect/pipeline/json_mapper.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/message_mapper.hpp b/src/mtconnect/pipeline/message_mapper.hpp index 49edb6f51..b9b3fa85d 100644 --- a/src/mtconnect/pipeline/message_mapper.hpp +++ b/src/mtconnect/pipeline/message_mapper.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/mtconnect_xml_transform.hpp b/src/mtconnect/pipeline/mtconnect_xml_transform.hpp index 269913336..84a191568 100644 --- a/src/mtconnect/pipeline/mtconnect_xml_transform.hpp +++ b/src/mtconnect/pipeline/mtconnect_xml_transform.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/period_filter.hpp b/src/mtconnect/pipeline/period_filter.hpp index 9073e8552..5746eaf98 100644 --- a/src/mtconnect/pipeline/period_filter.hpp +++ b/src/mtconnect/pipeline/period_filter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp index c16b9e334..388d803bc 100644 --- a/src/mtconnect/pipeline/pipeline.hpp +++ b/src/mtconnect/pipeline/pipeline.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/pipeline_context.hpp b/src/mtconnect/pipeline/pipeline_context.hpp index 42e55b554..17894961a 100644 --- a/src/mtconnect/pipeline/pipeline_context.hpp +++ b/src/mtconnect/pipeline/pipeline_context.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/pipeline_contract.hpp b/src/mtconnect/pipeline/pipeline_contract.hpp index 0e38733fc..ab9306ecf 100644 --- a/src/mtconnect/pipeline/pipeline_contract.hpp +++ b/src/mtconnect/pipeline/pipeline_contract.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/response_document.cpp b/src/mtconnect/pipeline/response_document.cpp index bbfe56b7f..d538488c6 100644 --- a/src/mtconnect/pipeline/response_document.cpp +++ b/src/mtconnect/pipeline/response_document.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/response_document.hpp b/src/mtconnect/pipeline/response_document.hpp index f61016526..2642012e2 100644 --- a/src/mtconnect/pipeline/response_document.hpp +++ b/src/mtconnect/pipeline/response_document.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/shdr_token_mapper.cpp b/src/mtconnect/pipeline/shdr_token_mapper.cpp index 7db74b6ca..efef18c9f 100644 --- a/src/mtconnect/pipeline/shdr_token_mapper.cpp +++ b/src/mtconnect/pipeline/shdr_token_mapper.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/shdr_token_mapper.hpp b/src/mtconnect/pipeline/shdr_token_mapper.hpp index f12c192d6..f80eb4335 100644 --- a/src/mtconnect/pipeline/shdr_token_mapper.hpp +++ b/src/mtconnect/pipeline/shdr_token_mapper.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/shdr_tokenizer.hpp b/src/mtconnect/pipeline/shdr_tokenizer.hpp index a9c3d31f0..d53353cae 100644 --- a/src/mtconnect/pipeline/shdr_tokenizer.hpp +++ b/src/mtconnect/pipeline/shdr_tokenizer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 0ce980aaa..651c77350 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/topic_mapper.hpp b/src/mtconnect/pipeline/topic_mapper.hpp index 7aa52887e..e6604157b 100644 --- a/src/mtconnect/pipeline/topic_mapper.hpp +++ b/src/mtconnect/pipeline/topic_mapper.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/transform.hpp b/src/mtconnect/pipeline/transform.hpp index 6fbf9d3a5..691e6ff20 100644 --- a/src/mtconnect/pipeline/transform.hpp +++ b/src/mtconnect/pipeline/transform.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/upcase_value.hpp b/src/mtconnect/pipeline/upcase_value.hpp index fae0d1a42..9ea8d2554 100644 --- a/src/mtconnect/pipeline/upcase_value.hpp +++ b/src/mtconnect/pipeline/upcase_value.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index ff3abf09d..21f692414 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -1,4 +1,4 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/json_printer.cpp b/src/mtconnect/printer/json_printer.cpp index 4dc7266cd..c7e699a44 100644 --- a/src/mtconnect/printer/json_printer.cpp +++ b/src/mtconnect/printer/json_printer.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/json_printer.hpp b/src/mtconnect/printer/json_printer.hpp index c4c1cd902..c5615b71f 100644 --- a/src/mtconnect/printer/json_printer.hpp +++ b/src/mtconnect/printer/json_printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/json_printer_helper.hpp b/src/mtconnect/printer/json_printer_helper.hpp index 61b843e33..a95c5e051 100644 --- a/src/mtconnect/printer/json_printer_helper.hpp +++ b/src/mtconnect/printer/json_printer_helper.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/printer.hpp b/src/mtconnect/printer/printer.hpp index 914b8e701..36a3394b7 100644 --- a/src/mtconnect/printer/printer.hpp +++ b/src/mtconnect/printer/printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/xml_helper.hpp b/src/mtconnect/printer/xml_helper.hpp index e1758ae55..9cbfbada1 100644 --- a/src/mtconnect/printer/xml_helper.hpp +++ b/src/mtconnect/printer/xml_helper.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index e57629818..2697cd680 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/xml_printer.hpp b/src/mtconnect/printer/xml_printer.hpp index 71dc5287a..ba7c1648d 100644 --- a/src/mtconnect/printer/xml_printer.hpp +++ b/src/mtconnect/printer/xml_printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/printer/xml_printer_helper.hpp b/src/mtconnect/printer/xml_printer_helper.hpp index 3ac60e91a..fb5d4b223 100644 --- a/src/mtconnect/printer/xml_printer_helper.hpp +++ b/src/mtconnect/printer/xml_printer_helper.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/embedded.cpp b/src/mtconnect/ruby/embedded.cpp index 29b45b2c6..f461d086b 100644 --- a/src/mtconnect/ruby/embedded.cpp +++ b/src/mtconnect/ruby/embedded.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/embedded.hpp b/src/mtconnect/ruby/embedded.hpp index 38a5a1102..ffd2cc432 100644 --- a/src/mtconnect/ruby/embedded.hpp +++ b/src/mtconnect/ruby/embedded.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_agent.hpp b/src/mtconnect/ruby/ruby_agent.hpp index 954fff2da..45b3b050c 100644 --- a/src/mtconnect/ruby/ruby_agent.hpp +++ b/src/mtconnect/ruby/ruby_agent.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_entity.hpp b/src/mtconnect/ruby/ruby_entity.hpp index 3ad5fde06..3d85fe376 100644 --- a/src/mtconnect/ruby/ruby_entity.hpp +++ b/src/mtconnect/ruby/ruby_entity.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_observation.hpp b/src/mtconnect/ruby/ruby_observation.hpp index df0dec968..8f5ed6722 100644 --- a/src/mtconnect/ruby/ruby_observation.hpp +++ b/src/mtconnect/ruby/ruby_observation.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_pipeline.hpp b/src/mtconnect/ruby/ruby_pipeline.hpp index 0e1bf5312..c924d0d2f 100644 --- a/src/mtconnect/ruby/ruby_pipeline.hpp +++ b/src/mtconnect/ruby/ruby_pipeline.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_smart_ptr.hpp b/src/mtconnect/ruby/ruby_smart_ptr.hpp index a8c23106f..d8a044e50 100644 --- a/src/mtconnect/ruby/ruby_smart_ptr.hpp +++ b/src/mtconnect/ruby/ruby_smart_ptr.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_transform.hpp b/src/mtconnect/ruby/ruby_transform.hpp index f1e9800e4..ad23ed08a 100644 --- a/src/mtconnect/ruby/ruby_transform.hpp +++ b/src/mtconnect/ruby/ruby_transform.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_type.hpp b/src/mtconnect/ruby/ruby_type.hpp index 087b0e8fd..3a81c70e5 100644 --- a/src/mtconnect/ruby/ruby_type.hpp +++ b/src/mtconnect/ruby/ruby_type.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/ruby/ruby_vm.hpp b/src/mtconnect/ruby/ruby_vm.hpp index faba18f23..88dbb1a5d 100644 --- a/src/mtconnect/ruby/ruby_vm.hpp +++ b/src/mtconnect/ruby/ruby_vm.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/cached_file.hpp b/src/mtconnect/sink/rest_sink/cached_file.hpp index 40efea4fb..95243f50b 100644 --- a/src/mtconnect/sink/rest_sink/cached_file.hpp +++ b/src/mtconnect/sink/rest_sink/cached_file.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/error.cpp b/src/mtconnect/sink/rest_sink/error.cpp index 2183594ab..b3ee21ca8 100644 --- a/src/mtconnect/sink/rest_sink/error.cpp +++ b/src/mtconnect/sink/rest_sink/error.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/error.hpp b/src/mtconnect/sink/rest_sink/error.hpp index 0a0edacea..72592b88e 100644 --- a/src/mtconnect/sink/rest_sink/error.hpp +++ b/src/mtconnect/sink/rest_sink/error.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/file_cache.cpp b/src/mtconnect/sink/rest_sink/file_cache.cpp index 07d31e182..4c1ae8f5c 100644 --- a/src/mtconnect/sink/rest_sink/file_cache.cpp +++ b/src/mtconnect/sink/rest_sink/file_cache.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/file_cache.hpp b/src/mtconnect/sink/rest_sink/file_cache.hpp index f5125ccd3..d0802f324 100644 --- a/src/mtconnect/sink/rest_sink/file_cache.hpp +++ b/src/mtconnect/sink/rest_sink/file_cache.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/parameter.hpp b/src/mtconnect/sink/rest_sink/parameter.hpp index 2c3c479e2..541e4dcd0 100644 --- a/src/mtconnect/sink/rest_sink/parameter.hpp +++ b/src/mtconnect/sink/rest_sink/parameter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/request.hpp b/src/mtconnect/sink/rest_sink/request.hpp index 9cf912b68..b96207faf 100644 --- a/src/mtconnect/sink/rest_sink/request.hpp +++ b/src/mtconnect/sink/rest_sink/request.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/response.hpp b/src/mtconnect/sink/rest_sink/response.hpp index 2e662f8da..a380c609a 100644 --- a/src/mtconnect/sink/rest_sink/response.hpp +++ b/src/mtconnect/sink/rest_sink/response.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index c57a9b0ff..73f331dc1 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/rest_service.hpp b/src/mtconnect/sink/rest_sink/rest_service.hpp index 3d5e7943c..17437f2fb 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.hpp +++ b/src/mtconnect/sink/rest_sink/rest_service.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/routing.hpp b/src/mtconnect/sink/rest_sink/routing.hpp index ea4f37ab6..16813c01c 100644 --- a/src/mtconnect/sink/rest_sink/routing.hpp +++ b/src/mtconnect/sink/rest_sink/routing.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp index dd34add95..550715817 100644 --- a/src/mtconnect/sink/rest_sink/server.cpp +++ b/src/mtconnect/sink/rest_sink/server.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/server.hpp b/src/mtconnect/sink/rest_sink/server.hpp index f025fa2b1..924adedd4 100644 --- a/src/mtconnect/sink/rest_sink/server.hpp +++ b/src/mtconnect/sink/rest_sink/server.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/session.hpp b/src/mtconnect/sink/rest_sink/session.hpp index 821d7ff88..653661c8d 100644 --- a/src/mtconnect/sink/rest_sink/session.hpp +++ b/src/mtconnect/sink/rest_sink/session.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/session_impl.cpp b/src/mtconnect/sink/rest_sink/session_impl.cpp index cdd04d2e8..e90a4c2cc 100644 --- a/src/mtconnect/sink/rest_sink/session_impl.cpp +++ b/src/mtconnect/sink/rest_sink/session_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/session_impl.hpp b/src/mtconnect/sink/rest_sink/session_impl.hpp index b890a6039..13eb59eac 100644 --- a/src/mtconnect/sink/rest_sink/session_impl.hpp +++ b/src/mtconnect/sink/rest_sink/session_impl.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/rest_sink/tls_dector.hpp b/src/mtconnect/sink/rest_sink/tls_dector.hpp index cf8065899..41c260e5e 100644 --- a/src/mtconnect/sink/rest_sink/tls_dector.hpp +++ b/src/mtconnect/sink/rest_sink/tls_dector.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/sink.cpp b/src/mtconnect/sink/sink.cpp index 1a65d10f9..ff9724417 100644 --- a/src/mtconnect/sink/sink.cpp +++ b/src/mtconnect/sink/sink.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/sink.hpp b/src/mtconnect/sink/sink.hpp index 416564933..8fed400ef 100644 --- a/src/mtconnect/sink/sink.hpp +++ b/src/mtconnect/sink/sink.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/adapter.hpp b/src/mtconnect/source/adapter/adapter.hpp index 7052d6d87..33c52a15f 100644 --- a/src/mtconnect/source/adapter/adapter.hpp +++ b/src/mtconnect/source/adapter/adapter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/adapter_pipeline.cpp b/src/mtconnect/source/adapter/adapter_pipeline.cpp index 0c0a8ada4..261ce5db1 100644 --- a/src/mtconnect/source/adapter/adapter_pipeline.cpp +++ b/src/mtconnect/source/adapter/adapter_pipeline.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/adapter_pipeline.hpp b/src/mtconnect/source/adapter/adapter_pipeline.hpp index a42a49112..fe5e448fb 100644 --- a/src/mtconnect/source/adapter/adapter_pipeline.hpp +++ b/src/mtconnect/source/adapter/adapter_pipeline.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index afc34b151..d358bd0ad 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.hpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.hpp index aed804f63..44e17c790 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/http_session.hpp b/src/mtconnect/source/adapter/agent_adapter/http_session.hpp index 9f887b8d0..08f25669c 100644 --- a/src/mtconnect/source/adapter/agent_adapter/http_session.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/http_session.hpp @@ -1,6 +1,6 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/https_session.hpp b/src/mtconnect/source/adapter/agent_adapter/https_session.hpp index a00323016..698821ba4 100644 --- a/src/mtconnect/source/adapter/agent_adapter/https_session.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/https_session.hpp @@ -1,6 +1,6 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/session.hpp b/src/mtconnect/source/adapter/agent_adapter/session.hpp index 7a321ad60..6cd87de9e 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session.hpp @@ -1,6 +1,6 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp index 41745fb7e..1557d46d3 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp @@ -1,6 +1,6 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index 583c979cc..bbb83703e 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.hpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.hpp index b0c932e66..e75211701 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.hpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/connector.cpp b/src/mtconnect/source/adapter/shdr/connector.cpp index fb7f5979d..ee813178d 100644 --- a/src/mtconnect/source/adapter/shdr/connector.cpp +++ b/src/mtconnect/source/adapter/shdr/connector.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/connector.hpp b/src/mtconnect/source/adapter/shdr/connector.hpp index d8cd727a8..c5d0fbb50 100644 --- a/src/mtconnect/source/adapter/shdr/connector.hpp +++ b/src/mtconnect/source/adapter/shdr/connector.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp index 695dc247c..d8a71b908 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp index 4b40f514c..15c25eba3 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/shdr_pipeline.cpp b/src/mtconnect/source/adapter/shdr/shdr_pipeline.cpp index 45116061e..2b7ab05f2 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_pipeline.cpp +++ b/src/mtconnect/source/adapter/shdr/shdr_pipeline.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/adapter/shdr/shdr_pipeline.hpp b/src/mtconnect/source/adapter/shdr/shdr_pipeline.hpp index f975ee4f2..0c65502d0 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_pipeline.hpp +++ b/src/mtconnect/source/adapter/shdr/shdr_pipeline.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/error_code.hpp b/src/mtconnect/source/error_code.hpp index af3164bb5..f62876641 100644 --- a/src/mtconnect/source/error_code.hpp +++ b/src/mtconnect/source/error_code.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/loopback_source.cpp b/src/mtconnect/source/loopback_source.cpp index 65e629ef1..e0acc0045 100644 --- a/src/mtconnect/source/loopback_source.cpp +++ b/src/mtconnect/source/loopback_source.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/loopback_source.hpp b/src/mtconnect/source/loopback_source.hpp index e12ee2282..f0b804889 100644 --- a/src/mtconnect/source/loopback_source.hpp +++ b/src/mtconnect/source/loopback_source.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/source.cpp b/src/mtconnect/source/source.cpp index eb1f8c59a..b2705a8c1 100644 --- a/src/mtconnect/source/source.cpp +++ b/src/mtconnect/source/source.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp index 4debdafb6..90394af85 100644 --- a/src/mtconnect/source/source.hpp +++ b/src/mtconnect/source/source.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/utilities.cpp b/src/mtconnect/utilities.cpp index e9d48a60e..e583e1f94 100644 --- a/src/mtconnect/utilities.cpp +++ b/src/mtconnect/utilities.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 07b46e6d3..ab36aa242 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/validation/observations.cpp b/src/mtconnect/validation/observations.cpp index 4f2825a5d..1c4e44eaa 100644 --- a/src/mtconnect/validation/observations.cpp +++ b/src/mtconnect/validation/observations.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/validation/observations.hpp b/src/mtconnect/validation/observations.hpp index 324f9ddd5..6f7b13ca9 100644 --- a/src/mtconnect/validation/observations.hpp +++ b/src/mtconnect/validation/observations.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/version.cpp b/src/mtconnect/version.cpp index 8a082fb36..23f72e6a5 100644 --- a/src/mtconnect/version.cpp +++ b/src/mtconnect/version.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/adapter_test.cpp b/test_package/adapter_test.cpp index 58712ec98..beb1cee70 100644 --- a/test_package/adapter_test.cpp +++ b/test_package/adapter_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_adapter_test.cpp b/test_package/agent_adapter_test.cpp index b086e69a0..21394cbaf 100644 --- a/test_package/agent_adapter_test.cpp +++ b/test_package/agent_adapter_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_asset_test.cpp b/test_package/agent_asset_test.cpp index e28b50ac1..bb934e015 100644 --- a/test_package/agent_asset_test.cpp +++ b/test_package/agent_asset_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_device_test.cpp b/test_package/agent_device_test.cpp index 7ff334c68..6eb74c5fb 100644 --- a/test_package/agent_device_test.cpp +++ b/test_package/agent_device_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 4bf95ad9b..2a889c87a 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_test_helper.cpp b/test_package/agent_test_helper.cpp index c1dd2ccb3..b2a466a4a 100644 --- a/test_package/agent_test_helper.cpp +++ b/test_package/agent_test_helper.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/agent_test_helper.hpp b/test_package/agent_test_helper.hpp index 16c7a5cf7..45ff79c00 100644 --- a/test_package/agent_test_helper.hpp +++ b/test_package/agent_test_helper.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/asset_buffer_test.cpp b/test_package/asset_buffer_test.cpp index 92efefea8..558c041a7 100644 --- a/test_package/asset_buffer_test.cpp +++ b/test_package/asset_buffer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/asset_hash_test.cpp b/test_package/asset_hash_test.cpp index ae17a1665..4705947ad 100644 --- a/test_package/asset_hash_test.cpp +++ b/test_package/asset_hash_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/asset_test.cpp b/test_package/asset_test.cpp index f5fd92825..6794d6676 100644 --- a/test_package/asset_test.cpp +++ b/test_package/asset_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/change_observer_test.cpp b/test_package/change_observer_test.cpp index d809fec6f..26d91807e 100644 --- a/test_package/change_observer_test.cpp +++ b/test_package/change_observer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/checkpoint_test.cpp b/test_package/checkpoint_test.cpp index f874ae5c4..8e09c0a60 100644 --- a/test_package/checkpoint_test.cpp +++ b/test_package/checkpoint_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/circular_buffer_test.cpp b/test_package/circular_buffer_test.cpp index 4e935c94b..776708be1 100644 --- a/test_package/circular_buffer_test.cpp +++ b/test_package/circular_buffer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/component_parameters_test.cpp b/test_package/component_parameters_test.cpp index 076175db9..625fa3df4 100644 --- a/test_package/component_parameters_test.cpp +++ b/test_package/component_parameters_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/component_test.cpp b/test_package/component_test.cpp index b4b8c1a87..a48b9bd71 100644 --- a/test_package/component_test.cpp +++ b/test_package/component_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/composition_test.cpp b/test_package/composition_test.cpp index 4501ac3f4..baa2d34c1 100644 --- a/test_package/composition_test.cpp +++ b/test_package/composition_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/config_parser_test.cpp b/test_package/config_parser_test.cpp index fab7e9ad6..569d19301 100644 --- a/test_package/config_parser_test.cpp +++ b/test_package/config_parser_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/config_test.cpp b/test_package/config_test.cpp index 29913b225..3934a7872 100644 --- a/test_package/config_test.cpp +++ b/test_package/config_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/connector_test.cpp b/test_package/connector_test.cpp index e40c351d8..016b8cbdf 100644 --- a/test_package/connector_test.cpp +++ b/test_package/connector_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/coordinate_system_test.cpp b/test_package/coordinate_system_test.cpp index 4a4f56535..3b362d05f 100644 --- a/test_package/coordinate_system_test.cpp +++ b/test_package/coordinate_system_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/correct_timestamp_test.cpp b/test_package/correct_timestamp_test.cpp index 685b2c051..48d0ba701 100644 --- a/test_package/correct_timestamp_test.cpp +++ b/test_package/correct_timestamp_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/cutting_tool_test.cpp b/test_package/cutting_tool_test.cpp index 4647cc036..17989ba9b 100644 --- a/test_package/cutting_tool_test.cpp +++ b/test_package/cutting_tool_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/data_item_mapping_test.cpp b/test_package/data_item_mapping_test.cpp index 167cdcbdb..380677dcc 100644 --- a/test_package/data_item_mapping_test.cpp +++ b/test_package/data_item_mapping_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/data_item_test.cpp b/test_package/data_item_test.cpp index 0b7385024..7932e387e 100644 --- a/test_package/data_item_test.cpp +++ b/test_package/data_item_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/data_set_test.cpp b/test_package/data_set_test.cpp index 24566788a..6302ea3b3 100644 --- a/test_package/data_set_test.cpp +++ b/test_package/data_set_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/device_test.cpp b/test_package/device_test.cpp index 6bb14a651..faa671fc2 100644 --- a/test_package/device_test.cpp +++ b/test_package/device_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/duplicate_filter_test.cpp b/test_package/duplicate_filter_test.cpp index 3d891457c..cc9e0d8f4 100644 --- a/test_package/duplicate_filter_test.cpp +++ b/test_package/duplicate_filter_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/embedded_ruby_test.cpp b/test_package/embedded_ruby_test.cpp index b83e4e24e..326aa7bcd 100644 --- a/test_package/embedded_ruby_test.cpp +++ b/test_package/embedded_ruby_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/entity_parser_test.cpp b/test_package/entity_parser_test.cpp index a5aa43e1e..61213d293 100644 --- a/test_package/entity_parser_test.cpp +++ b/test_package/entity_parser_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/entity_printer_test.cpp b/test_package/entity_printer_test.cpp index 2999fe1e3..43a7fe29f 100644 --- a/test_package/entity_printer_test.cpp +++ b/test_package/entity_printer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/entity_test.cpp b/test_package/entity_test.cpp index 88396d8de..2cf031928 100644 --- a/test_package/entity_test.cpp +++ b/test_package/entity_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/file_asset_test.cpp b/test_package/file_asset_test.cpp index 9b5a753e9..2c1172399 100644 --- a/test_package/file_asset_test.cpp +++ b/test_package/file_asset_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/file_cache_test.cpp b/test_package/file_cache_test.cpp index 5e916fd28..439668666 100644 --- a/test_package/file_cache_test.cpp +++ b/test_package/file_cache_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/fixture_test.cpp b/test_package/fixture_test.cpp index 36cc02e8f..48f711f3d 100644 --- a/test_package/fixture_test.cpp +++ b/test_package/fixture_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/http_server_test.cpp b/test_package/http_server_test.cpp index fa2607c58..8535aec34 100644 --- a/test_package/http_server_test.cpp +++ b/test_package/http_server_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/image_file_test.cpp b/test_package/image_file_test.cpp index 9a1ebfe67..339878370 100644 --- a/test_package/image_file_test.cpp +++ b/test_package/image_file_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_helper.hpp b/test_package/json_helper.hpp index 5ec636147..71d74fb08 100644 --- a/test_package/json_helper.hpp +++ b/test_package/json_helper.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_mapping_test.cpp b/test_package/json_mapping_test.cpp index b4d06be49..7b9e8f31c 100644 --- a/test_package/json_mapping_test.cpp +++ b/test_package/json_mapping_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_parser_test.cpp b/test_package/json_parser_test.cpp index 5db25737f..8bbd86d56 100644 --- a/test_package/json_parser_test.cpp +++ b/test_package/json_parser_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_printer_asset_test.cpp b/test_package/json_printer_asset_test.cpp index d2e61b8f6..fa65800b7 100644 --- a/test_package/json_printer_asset_test.cpp +++ b/test_package/json_printer_asset_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_printer_error_test.cpp b/test_package/json_printer_error_test.cpp index 0fb0166f5..fa307ee52 100644 --- a/test_package/json_printer_error_test.cpp +++ b/test_package/json_printer_error_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_printer_probe_test.cpp b/test_package/json_printer_probe_test.cpp index 1760aea92..978336217 100644 --- a/test_package/json_printer_probe_test.cpp +++ b/test_package/json_printer_probe_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_printer_stream_test.cpp b/test_package/json_printer_stream_test.cpp index 40e85c5bf..8ae04ad3f 100644 --- a/test_package/json_printer_stream_test.cpp +++ b/test_package/json_printer_stream_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/json_printer_test.cpp b/test_package/json_printer_test.cpp index 1fbb276f1..157c9274e 100644 --- a/test_package/json_printer_test.cpp +++ b/test_package/json_printer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/kinematics_test.cpp b/test_package/kinematics_test.cpp index 1c9c462c8..15fe9aa10 100644 --- a/test_package/kinematics_test.cpp +++ b/test_package/kinematics_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/mqtt_adapter_test.cpp b/test_package/mqtt_adapter_test.cpp index da4efeddb..080d09e92 100644 --- a/test_package/mqtt_adapter_test.cpp +++ b/test_package/mqtt_adapter_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/mqtt_isolated_test.cpp b/test_package/mqtt_isolated_test.cpp index 9789436c5..aecdf3b55 100644 --- a/test_package/mqtt_isolated_test.cpp +++ b/test_package/mqtt_isolated_test.cpp @@ -1,5 +1,5 @@ -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/mqtt_sink_test.cpp b/test_package/mqtt_sink_test.cpp index 0da157089..d544b579c 100644 --- a/test_package/mqtt_sink_test.cpp +++ b/test_package/mqtt_sink_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/mtconnect_xml_transform_test.cpp b/test_package/mtconnect_xml_transform_test.cpp index 2c32d004c..53730c8dc 100644 --- a/test_package/mtconnect_xml_transform_test.cpp +++ b/test_package/mtconnect_xml_transform_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/observation_test.cpp b/test_package/observation_test.cpp index 3040dfe31..15194982d 100644 --- a/test_package/observation_test.cpp +++ b/test_package/observation_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index bd45fbab2..df0be79f3 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/pallet_test.cpp b/test_package/pallet_test.cpp index a8eb5f5ce..40b382bde 100644 --- a/test_package/pallet_test.cpp +++ b/test_package/pallet_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/period_filter_test.cpp b/test_package/period_filter_test.cpp index 49ee0833c..f1d39ecc5 100644 --- a/test_package/period_filter_test.cpp +++ b/test_package/period_filter_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/physical_asset_test.cpp b/test_package/physical_asset_test.cpp index f451f03a3..3a66fd587 100644 --- a/test_package/physical_asset_test.cpp +++ b/test_package/physical_asset_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/pipeline_deliver_test.cpp b/test_package/pipeline_deliver_test.cpp index 3e632ee37..1584b0497 100644 --- a/test_package/pipeline_deliver_test.cpp +++ b/test_package/pipeline_deliver_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/pipeline_edit_test.cpp b/test_package/pipeline_edit_test.cpp index c5b68d90f..5268fec63 100644 --- a/test_package/pipeline_edit_test.cpp +++ b/test_package/pipeline_edit_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/qif_document_test.cpp b/test_package/qif_document_test.cpp index 68097689f..42d552c3e 100644 --- a/test_package/qif_document_test.cpp +++ b/test_package/qif_document_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/qname_test.cpp b/test_package/qname_test.cpp index 05ac55b35..0cc873b4a 100644 --- a/test_package/qname_test.cpp +++ b/test_package/qname_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/raw_material_test.cpp b/test_package/raw_material_test.cpp index e7edecf6d..fab3a2feb 100644 --- a/test_package/raw_material_test.cpp +++ b/test_package/raw_material_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/references_test.cpp b/test_package/references_test.cpp index 7d18c1148..2b79f546c 100644 --- a/test_package/references_test.cpp +++ b/test_package/references_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/relationship_test.cpp b/test_package/relationship_test.cpp index b9ebf7f07..94174d5b8 100644 --- a/test_package/relationship_test.cpp +++ b/test_package/relationship_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/response_document_test.cpp b/test_package/response_document_test.cpp index f02f3f0db..55c6c793d 100644 --- a/test_package/response_document_test.cpp +++ b/test_package/response_document_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/routing_test.cpp b/test_package/routing_test.cpp index 57c06fef1..3235cea32 100644 --- a/test_package/routing_test.cpp +++ b/test_package/routing_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/sensor_configuration_test.cpp b/test_package/sensor_configuration_test.cpp index eaf783b65..4ea1ddd74 100644 --- a/test_package/sensor_configuration_test.cpp +++ b/test_package/sensor_configuration_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/shdr_tokenizer_test.cpp b/test_package/shdr_tokenizer_test.cpp index ecbea97e8..570015fe8 100644 --- a/test_package/shdr_tokenizer_test.cpp +++ b/test_package/shdr_tokenizer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/solid_model_test.cpp b/test_package/solid_model_test.cpp index 7cd6696a7..4e81d23d1 100644 --- a/test_package/solid_model_test.cpp +++ b/test_package/solid_model_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/specification_test.cpp b/test_package/specification_test.cpp index 3e3529fd6..fe7ab06d8 100644 --- a/test_package/specification_test.cpp +++ b/test_package/specification_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/table_test.cpp b/test_package/table_test.cpp index f424a12e7..d27b14edd 100644 --- a/test_package/table_test.cpp +++ b/test_package/table_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/test_utilities.hpp b/test_package/test_utilities.hpp index c40b39f42..21ba8f892 100644 --- a/test_package/test_utilities.hpp +++ b/test_package/test_utilities.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/testadapter_service.cpp b/test_package/testadapter_service.cpp index 23ae60b83..a78130bbf 100644 --- a/test_package/testadapter_service.cpp +++ b/test_package/testadapter_service.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/testadapter_service.hpp b/test_package/testadapter_service.hpp index d0828588a..558cdf30e 100644 --- a/test_package/testadapter_service.hpp +++ b/test_package/testadapter_service.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/testsink_service.cpp b/test_package/testsink_service.cpp index 245c9f595..cd6a421ed 100644 --- a/test_package/testsink_service.cpp +++ b/test_package/testsink_service.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/testsink_service.hpp b/test_package/testsink_service.hpp index 9b79806e2..6cfa35559 100644 --- a/test_package/testsink_service.hpp +++ b/test_package/testsink_service.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/timestamp_extractor_test.cpp b/test_package/timestamp_extractor_test.cpp index 9c9e2a172..99a314d63 100644 --- a/test_package/timestamp_extractor_test.cpp +++ b/test_package/timestamp_extractor_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index 830d85f06..aa2f560bc 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/topic_mapping_test.cpp b/test_package/topic_mapping_test.cpp index a83824e5e..19b928af2 100644 --- a/test_package/topic_mapping_test.cpp +++ b/test_package/topic_mapping_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/unit_conversion_test.cpp b/test_package/unit_conversion_test.cpp index 7a09bbc31..72438ecf5 100644 --- a/test_package/unit_conversion_test.cpp +++ b/test_package/unit_conversion_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/url_parser_test.cpp b/test_package/url_parser_test.cpp index bf7199a99..a9d646a9d 100644 --- a/test_package/url_parser_test.cpp +++ b/test_package/url_parser_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/utilities_test.cpp b/test_package/utilities_test.cpp index de43e88f4..d26188e8b 100644 --- a/test_package/utilities_test.cpp +++ b/test_package/utilities_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index d225449b8..856c2f147 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/xml_parser_test.cpp b/test_package/xml_parser_test.cpp index f2c532b94..88e61545a 100644 --- a/test_package/xml_parser_test.cpp +++ b/test_package/xml_parser_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test_package/xml_printer_test.cpp b/test_package/xml_printer_test.cpp index 8cc4ea6bf..287344fb1 100644 --- a/test_package/xml_printer_test.cpp +++ b/test_package/xml_printer_test.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); From fb3297a4603b660ba6b44104eb3c7aba6e9cd7d3 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Mon, 22 Sep 2025 14:58:44 +0200 Subject: [PATCH 08/84] Made a few utility functions constexpr --- src/mtconnect/utilities.hpp | 6 +++--- test_package/utilities_test.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index ab36aa242..d12b4cd0c 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -164,7 +164,7 @@ namespace mtconnect { /// @brief Convert text to upper case /// @param[in,out] text text /// @return upper-case of text as string - inline std::string toUpperCase(std::string &text) + constexpr std::string &toUpperCase(std::string &text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::toupper(c); }); @@ -175,7 +175,7 @@ namespace mtconnect { /// @brief Simple check if a number as a string is negative /// @param s the numbeer /// @return `true` if positive - inline bool isNonNegativeInteger(const std::string &s) + constexpr bool isNonNegativeInteger(const std::string &s) { for (const char c : s) { @@ -189,7 +189,7 @@ namespace mtconnect { /// @brief Checks if a string is a valid integer /// @param s the string /// @return `true` if is `[+-]\d+` - inline bool isInteger(const std::string &s) + constexpr bool isInteger(const std::string &s) { auto iter = s.cbegin(); if (*iter == '-' || *iter == '+') diff --git a/test_package/utilities_test.cpp b/test_package/utilities_test.cpp index d26188e8b..4f43b903c 100644 --- a/test_package/utilities_test.cpp +++ b/test_package/utilities_test.cpp @@ -34,7 +34,7 @@ int main(int argc, char *argv[]) return RUN_ALL_TESTS(); } -TEST(UtilitiesTest, IntToString) +TEST(UtilitiesTest, should_be_able_to_convert_from_integer_to_string) { ASSERT_EQ((string) "1234", to_string(1234)); ASSERT_EQ((string) "0", to_string(0)); @@ -42,7 +42,7 @@ TEST(UtilitiesTest, IntToString) ASSERT_EQ((string) "1", to_string(1)); } -TEST(UtilitiesTest, FloatToString) +TEST(UtilitiesTest, should_be_able_to_convert_from_float_to_string) { ASSERT_EQ((string) "1.234", format(1.234)); ASSERT_EQ((string) "0", format(0.0)); @@ -50,7 +50,7 @@ TEST(UtilitiesTest, FloatToString) ASSERT_EQ((string) "1", format(1.0)); } -TEST(UtilitiesTest, ToUpperCase) +TEST(UtilitiesTest, should_uppercase_string) { string lower = "abcDef"; ASSERT_EQ((string) "ABCDEF", toUpperCase(lower)); From 4e46fe3c6f107fce47ae7a3a4c185a0ad9290958 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 23 Sep 2025 20:28:38 +0200 Subject: [PATCH 09/84] Fixed issues related to changes in spawn and sha1 processing --- conan/mqtt_cpp/conanfile.py | 4 +- conanfile.py | 2 +- src/mtconnect/configuration/agent_config.cpp | 5 +- src/mtconnect/entity/entity.hpp | 2 +- src/mtconnect/pipeline/deliver.cpp | 2 +- src/mtconnect/pipeline/pipeline.hpp | 3 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 17 +++---- .../adapter/agent_adapter/agent_adapter.cpp | 11 +---- .../source/adapter/mqtt/mqtt_adapter.cpp | 11 +---- .../source/adapter/shdr/connector.cpp | 20 ++++---- .../source/adapter/shdr/connector.hpp | 2 +- .../source/adapter/shdr/shdr_adapter.cpp | 9 +--- src/mtconnect/source/source.cpp | 21 +++++++++ src/mtconnect/source/source.hpp | 5 ++ src/mtconnect/utilities.cpp | 13 ++---- src/mtconnect/utilities.hpp | 2 +- test_package/config_test.cpp | 46 +++++++++---------- test_package/file_cache_test.cpp | 2 +- test_package/http_server_test.cpp | 13 ++++-- test_package/mqtt_isolated_test.cpp | 2 +- test_package/period_filter_test.cpp | 12 ++--- test_package/tls_http_server_test.cpp | 9 ++-- test_package/websockets_test.cpp | 25 ++++++---- 23 files changed, 123 insertions(+), 115 deletions(-) diff --git a/conan/mqtt_cpp/conanfile.py b/conan/mqtt_cpp/conanfile.py index 35450edf5..f5051e99f 100644 --- a/conan/mqtt_cpp/conanfile.py +++ b/conan/mqtt_cpp/conanfile.py @@ -4,13 +4,13 @@ class MqttcppConan(ConanFile): name = "mqtt_cpp" - version = "13.2.1" + version = "13.2.2" license = "Boost Software License, Version 1.0" author = "Takatoshi Kondo redboltz@gmail.com" url = "https://github.com/redboltz/mqtt_cpp" description = "MQTT client/server for C++14 based on Boost.Asio" topics = ("mqtt") - requires = ["boost/1.85.0"] + requires = ["boost/1.88.0"] no_copy_source = True exports_sources = "include/*" diff --git a/conanfile.py b/conanfile.py index 902bc4f0e..85314cc57 100644 --- a/conanfile.py +++ b/conanfile.py @@ -118,7 +118,7 @@ def build_requirements(self): self.tool_requires_version("doxygen", [1, 14, 0]) def requirements(self): - self.requires("boost/1.85.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) + self.requires("boost/1.88.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("libxml2/2.10.3", headers=True, libs=True, visible=True, transitive_headers=True, transitive_libs=True) self.requires("date/3.0.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("nlohmann_json/3.9.1", headers=True, libs=False, transitive_headers=True, transitive_libs=False) diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 2c7a19d9d..48ee6f8b2 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -366,7 +366,7 @@ namespace mtconnect::configuration { using std::placeholders::_1; - m_monitorTimer.expires_from_now(100ms); + m_monitorTimer.expires_after(100ms); m_monitorTimer.async_wait(boost::bind(&AgentConfiguration::monitorFiles, this, _1)); } else @@ -388,7 +388,7 @@ namespace mtconnect::configuration { using std::placeholders::_1; - m_monitorTimer.expires_from_now(m_monitorInterval); + m_monitorTimer.expires_after(m_monitorInterval); m_monitorTimer.async_wait(boost::bind(&AgentConfiguration::monitorFiles, this, _1)); } @@ -1252,3 +1252,4 @@ namespace mtconnect::configuration { return false; } } // namespace mtconnect::configuration + diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index b83bf6352..c70872596 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -408,7 +408,7 @@ namespace mtconnect { boost::uuids::detail::sha1 sha1; hash(sha1); - unsigned int digest[5]; + unsigned char digest[20]; sha1.get_digest(digest); char encoded[32]; diff --git a/src/mtconnect/pipeline/deliver.cpp b/src/mtconnect/pipeline/deliver.cpp index 8b6a96d35..c9d0e6648 100644 --- a/src/mtconnect/pipeline/deliver.cpp +++ b/src/mtconnect/pipeline/deliver.cpp @@ -115,7 +115,7 @@ namespace mtconnect { } using std::placeholders::_1; - m_timer.expires_from_now(10s); + m_timer.expires_after(10s); m_timer.async_wait( boost::asio::bind_executor(m_strand, boost::bind(&ComputeMetrics::compute, ptr(), _1))); } diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp index 388d803bc..8fa7b8594 100644 --- a/src/mtconnect/pipeline/pipeline.hpp +++ b/src/mtconnect/pipeline/pipeline.hpp @@ -18,6 +18,7 @@ #pragma once #include +#include #include "mtconnect/config.hpp" #include "pipeline_context.hpp" @@ -95,7 +96,7 @@ namespace mtconnect { { std::promise p; auto f = p.get_future(); - m_strand.dispatch([this, &p]() { + boost::asio::dispatch(m_strand, [this, &p]() { clearTransforms(); p.set_value(); }); diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 73f331dc1..46775087c 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -366,28 +366,24 @@ namespace mtconnect { if (!host.empty()) { // Check if it is a simple numeric address - using br = ip::resolver_base; boost::system::error_code ec; auto addr = ip::make_address(host, ec); if (ec) { ip::tcp::resolver resolver(m_context); - ip::tcp::resolver::query query(host, "0", br::v4_mapped); - - auto it = resolver.resolve(query, ec); + auto results = resolver.resolve(host, "0", ip::resolver_base::flags::v4_mapped, ec); if (ec) { cout << "Failed to resolve " << host << ": " << ec.message() << endl; } else { - ip::tcp::resolver::iterator end; - for (; it != end; it++) + for (const auto &res : results) { - const auto &addr = it->endpoint().address(); - if (!addr.is_multicast() && !addr.is_unspecified()) + const auto &a = res.endpoint().address(); + if (!a.is_multicast() && !a.is_unspecified()) { - m_server->allowPutFrom(addr.to_string()); + m_server->allowPutFrom(a.to_string()); } } } @@ -1229,7 +1225,7 @@ namespace mtconnect { boost::asio::bind_executor( m_strand, [this, asyncResponse]() { - asyncResponse->m_timer.expires_from_now(asyncResponse->getInterval()); + asyncResponse->m_timer.expires_after(asyncResponse->getInterval()); asyncResponse->m_timer.async_wait(boost::asio::bind_executor( m_strand, boost::bind(&RestService::streamNextCurrent, this, asyncResponse, _1))); @@ -1620,3 +1616,4 @@ namespace mtconnect { } // namespace sink::rest_sink } // namespace mtconnect + diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index d358bd0ad..b99bff088 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -134,15 +134,8 @@ namespace mtconnect::source::adapter::agent_adapter { } m_name = m_url.getUrlText(m_sourceDevice); - boost::uuids::detail::sha1 sha1; - sha1.process_bytes(m_name.c_str(), m_name.length()); - boost::uuids::detail::sha1::digest_type digest; - sha1.get_digest(digest); - - stringstream identity; - identity << std::hex << digest[0] << digest[1] << digest[2]; - m_identity = string("_") + (identity.str()).substr(0, 10); - + m_identity = CreateIdentityHash(m_name); + m_options.insert_or_assign(configuration::AdapterIdentity, m_identity); m_feedbackId = "XmlTransformFeedback:" + m_identity; diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index bbb83703e..fbbaf873d 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -166,16 +166,7 @@ namespace mtconnect { identity << s; } - boost::uuids::detail::sha1 sha1; - sha1.process_bytes(identity.str().c_str(), identity.str().length()); - boost::uuids::detail::sha1::digest_type digest; - sha1.get_digest(digest); - - identity.str(""); - identity << std::hex << digest[0] << digest[1] << digest[2]; - m_identity = string("_") + (identity.str()).substr(0, 10); - - m_options[configuration::AdapterIdentity] = m_identity; + m_options[configuration::AdapterIdentity] = CreateIdentityHash(identity.str()); } m_pipeline.build(m_options); diff --git a/src/mtconnect/source/adapter/shdr/connector.cpp b/src/mtconnect/source/adapter/shdr/connector.cpp index ee813178d..8345eab3f 100644 --- a/src/mtconnect/source/adapter/shdr/connector.cpp +++ b/src/mtconnect/source/adapter/shdr/connector.cpp @@ -103,7 +103,7 @@ namespace mtconnect::source::adapter::shdr { LOG(error) << "Will retry resolution of " << m_server << " in " << m_reconnectInterval.count() << " milliseconds"; - m_timer.expires_from_now(m_reconnectInterval); + m_timer.expires_after(m_reconnectInterval); m_timer.async_wait([this](boost::system::error_code ec) { if (ec != boost::asio::error::operation_aborted) { @@ -131,11 +131,7 @@ namespace mtconnect::source::adapter::shdr { // Using a smart pointer to ensure connection is deleted if exception thrown LOG(debug) << "Connecting to data source: " << m_server << " on port: " << m_port; - asio::async_connect( - m_socket, m_results.begin(), m_results.end(), - [this](const boost::system::error_code &ec, ip::tcp::resolver::iterator it) { - asio::dispatch(m_strand, boost::bind(&Connector::connected, this, ec, it)); - }); + asio::async_connect(m_socket, m_results, boost::bind(&Connector::connected, this, _1, _2)); return true; } @@ -144,7 +140,7 @@ namespace mtconnect::source::adapter::shdr { { NAMED_SCOPE("Connector::asyncTryConnect"); - m_timer.expires_from_now(m_reconnectInterval); + m_timer.expires_after(m_reconnectInterval); m_timer.async_wait([this](boost::system::error_code ec) { if (ec != boost::asio::error::operation_aborted) { @@ -176,7 +172,7 @@ namespace mtconnect::source::adapter::shdr { asyncTryConnect(); } - void Connector::connected(const boost::system::error_code &ec, ip::tcp::resolver::iterator it) + void Connector::connected(const boost::system::error_code &ec, const ip::tcp::endpoint &endpoint) { NAMED_SCOPE("Connector::connected"); @@ -229,7 +225,7 @@ namespace mtconnect::source::adapter::shdr { while (parseSocketBuffer()) ; - m_timer.expires_from_now(m_receiveTimeLimit); + m_timer.expires_after(m_receiveTimeLimit); m_timer.async_wait([this](boost::system::error_code ec) { if (ec != boost::asio::error::operation_aborted) { @@ -267,7 +263,7 @@ namespace mtconnect::source::adapter::shdr { { NAMED_SCOPE("Connector::setReceiveTimeout"); - m_receiveTimeout.expires_from_now(m_receiveTimeLimit); + m_receiveTimeout.expires_after(m_receiveTimeLimit); m_receiveTimeout.async_wait([this](sys::error_code ec) { if (!ec) { @@ -385,7 +381,7 @@ namespace mtconnect::source::adapter::shdr { { LOG(debug) << "Sending heartbeat"; sendCommand("PING"); - m_heartbeatTimer.expires_from_now(m_heartbeatFrequency); + m_heartbeatTimer.expires_after(m_heartbeatFrequency); m_heartbeatTimer.async_wait([this](boost::system::error_code ec) { asio::dispatch(m_strand, boost::bind(&Connector::heartbeat, this, ec)); }); @@ -420,7 +416,7 @@ namespace mtconnect::source::adapter::shdr { m_receiveTimeLimit = 2 * m_heartbeatFrequency; setReceiveTimeout(); - m_heartbeatTimer.expires_from_now(m_heartbeatFrequency); + m_heartbeatTimer.expires_after(m_heartbeatFrequency); m_heartbeatTimer.async_wait([this](boost::system::error_code ec) { asio::dispatch(m_strand, boost::bind(&Connector::heartbeat, this, ec)); }); diff --git a/src/mtconnect/source/adapter/shdr/connector.hpp b/src/mtconnect/source/adapter/shdr/connector.hpp index c5d0fbb50..ad0e9ed14 100644 --- a/src/mtconnect/source/adapter/shdr/connector.hpp +++ b/src/mtconnect/source/adapter/shdr/connector.hpp @@ -105,7 +105,7 @@ namespace mtconnect::source::adapter::shdr { void resolved(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results); void connected(const boost::system::error_code &error, - boost::asio::ip::tcp::resolver::iterator it); + const boost::asio::ip::tcp::endpoint& endpoint); void writer(boost::system::error_code ec, std::size_t length); void reader(boost::system::error_code ec, std::size_t length); bool parseSocketBuffer(); diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp index d8a71b908..0a614b6d3 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp @@ -89,14 +89,7 @@ namespace mtconnect::source::adapter::shdr { { if (IsOptionSet(m_options, configuration::SuppressIPAddress)) { - boost::uuids::detail::sha1 sha1; - sha1.process_bytes(identity.str().c_str(), identity.str().length()); - boost::uuids::detail::sha1::digest_type digest; - sha1.get_digest(digest); - - identity.str(""); - identity << std::hex << digest[0] << digest[1] << digest[2]; - m_identity = string("_") + (identity.str()).substr(0, 10); + m_identity = CreateIdentityHash(identity.str()); } else { diff --git a/src/mtconnect/source/source.cpp b/src/mtconnect/source/source.cpp index b2705a8c1..d67880b97 100644 --- a/src/mtconnect/source/source.cpp +++ b/src/mtconnect/source/source.cpp @@ -15,8 +15,12 @@ // limitations under the License. // +#include + #include "mtconnect/source/source.hpp" +#include + #include "mtconnect/logging.hpp" namespace mtconnect::source { @@ -38,4 +42,21 @@ namespace mtconnect::source { return nullptr; } + std::string CreateIdentityHash(const std::string &input) + { + using namespace std; + + boost::uuids::detail::sha1 sha1; + sha1.process_bytes(input.c_str(), input.length()); + boost::uuids::detail::sha1::digest_type digest; + sha1.get_digest(digest); + + ostringstream identity; + identity << '_' << std::hex; + for (int i = 0; i < 5; i++) + identity << (uint16_t) digest[i]; + + return identity.str(); + } + } // namespace mtconnect::source diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp index 90394af85..8ff888bb4 100644 --- a/src/mtconnect/source/source.hpp +++ b/src/mtconnect/source/source.hpp @@ -95,6 +95,11 @@ namespace mtconnect { std::string m_name; boost::asio::io_context::strand m_strand; }; + + /// @brief create a unique identity hash for an XML id starting with an `_` and 10 hex digits + /// @param text the text to create the hashed id + /// @returns a string with the hashed result + AGENT_LIB_API std::string CreateIdentityHash(const std::string &input); /// @brief A factory for creating the source class AGENT_LIB_API SourceFactory diff --git a/src/mtconnect/utilities.cpp b/src/mtconnect/utilities.cpp index e583e1f94..d6fb420c8 100644 --- a/src/mtconnect/utilities.cpp +++ b/src/mtconnect/utilities.cpp @@ -150,13 +150,13 @@ namespace mtconnect { { using namespace boost; using namespace asio; - using res = ip::udp::resolver; + using res = ip::tcp::resolver; string address; boost::system::error_code ec; res resolver(context); - auto iter = resolver.resolve(ip::host_name(), "5000", res::flags::address_configured, ec); + auto results = resolver.resolve(ip::host_name(), "5000", res::flags::address_configured, ec); if (ec) { LOG(warning) << "Cannot find IP address: " << ec.message(); @@ -164,11 +164,9 @@ namespace mtconnect { } else { - res::iterator end; - while (iter != end) + for (auto &res : results) { - const auto &ep = iter->endpoint(); - const auto &ad = ep.address(); + const auto &ad = res.endpoint().address(); if (!ad.is_unspecified() && !ad.is_loopback() && (!onlyV4 || !ad.is_v6())) { auto ads {ad.to_string()}; @@ -178,7 +176,6 @@ namespace mtconnect { address = ads; } } - iter++; } } @@ -210,7 +207,7 @@ namespace mtconnect { /// @return a boost ipv6 address static boost::asio::ip::address_v6 from_v6_string(std::vector str) { - return boost::asio::ip::address_v6::from_string(str.data()); + return boost::asio::ip::make_address_v6(str.data()); } BOOST_PHOENIX_ADAPT_FUNCTION(boost::asio::ip::address_v4, v4_from_4number, from_four_number, 4) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index d12b4cd0c..bd7efff08 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -827,7 +827,7 @@ namespace mtconnect { }; sha1.process_bytes(id.data(), id.length()); - unsigned int digest[5]; + unsigned char digest[20]; sha1.get_digest(digest); string s(32, ' '); diff --git a/test_package/config_test.cpp b/test_package/config_test.cpp index 3934a7872..fde6e2f1b 100644 --- a/test_package/config_test.cpp +++ b/test_package/config_test.cpp @@ -1006,7 +1006,7 @@ Port = 0 ASSERT_EQ("SPINDLE_SPEED", dataItem->getType()); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, &devices, agent](boost::system::error_code ec) { if (ec) { @@ -1025,7 +1025,7 @@ Port = 0 }); boost::asio::steady_timer timer2(context.get()); - timer2.expires_from_now(6s); + timer2.expires_after(6s); timer2.async_wait([this, agent, &chg](boost::system::error_code ec) { if (!ec) { @@ -1085,7 +1085,7 @@ Port = 0 ASSERT_EQ("SPINDLE_SPEED", dataItem->getType()); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, &devices](boost::system::error_code ec) { if (ec) { @@ -1098,7 +1098,7 @@ Port = 0 }); boost::asio::steady_timer timer2(context.get()); - timer2.expires_from_now(6s); + timer2.expires_after(6s); timer2.async_wait([this, &chg](boost::system::error_code ec) { if (!ec) { @@ -1151,7 +1151,7 @@ Port = 0 auto instance = rest->instanceId(); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, &config](boost::system::error_code ec) { if (ec) { @@ -1167,7 +1167,7 @@ Port = 0 this_thread::sleep_for(5s); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, agent, instance](boost::system::error_code ec) { if (!ec) { @@ -1228,7 +1228,7 @@ Port = 0 DataItemPtr di; boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, &devices](boost::system::error_code ec) { if (ec) { @@ -1242,7 +1242,7 @@ Port = 0 }); boost::asio::steady_timer timer2(context.get()); - timer2.expires_from_now(6s); + timer2.expires_after(6s); timer2.async_wait([this](boost::system::error_code ec) { if (!ec) { @@ -1345,7 +1345,7 @@ Port = 0 ASSERT_EQ("1.2", *printer->getSchemaVersion()); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, &devices, agent](boost::system::error_code ec) { if (ec) { @@ -1368,7 +1368,7 @@ Port = 0 this_thread::sleep_for(5s); boost::asio::steady_timer timer1(context.get()); - timer1.expires_from_now(1s); + timer1.expires_after(1s); timer1.async_wait([this, agent, instance](boost::system::error_code ec) { if (!ec) { @@ -1501,13 +1501,13 @@ Adapters { )"); adapter->processData("--multiline--AAAAA"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); @@ -1634,13 +1634,13 @@ Port = 0 )"); adapter->processData("--multiline--AAAAA"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); @@ -1807,13 +1807,13 @@ Port = 0 )"); adapter->processData("--multiline--AAAAA"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); @@ -1913,13 +1913,13 @@ Adapters { )"); adapter->processData("--multiline--AAAAA"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); @@ -2003,7 +2003,7 @@ Port = 0 ASSERT_EQ("001", pipeline->getDevice()); } - shutdownTimer.expires_from_now(3s); + shutdownTimer.expires_after(3s); shutdownTimer.async_wait(shudown); }; @@ -2039,13 +2039,13 @@ Port = 0 )"); adapter->processData("--multiline--AAAAA"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); @@ -2116,13 +2116,13 @@ Adapters { adapter->processData("* device: none"); adapter->processData("* uuid: 12345"); - timer2.expires_from_now(500ms); + timer2.expires_after(500ms); timer2.async_wait(validate); } }; boost::asio::steady_timer timer1(asyncContext.get()); - timer1.expires_from_now(100ms); + timer1.expires_after(100ms); timer1.async_wait(send); m_config->start(); diff --git a/test_package/file_cache_test.cpp b/test_package/file_cache_test.cpp index 439668666..a750463fd 100644 --- a/test_package/file_cache_test.cpp +++ b/test_package/file_cache_test.cpp @@ -180,7 +180,7 @@ TEST_F(FileCacheTest, file_cache_should_compress_file_async) }); bool ran {false}; - context.post([&ran] { ran = true; }); + boost::asio::post(context, [&ran] { ran = true; }); context.run(); // EXPECT_TRUE(ran); diff --git a/test_package/http_server_test.cpp b/test_package/http_server_test.cpp index 8535aec34..ff73298c3 100644 --- a/test_package/http_server_test.cpp +++ b/test_package/http_server_test.cpp @@ -42,6 +42,7 @@ using namespace mtconnect::sink::rest_sink; namespace asio = boost::asio; namespace beast = boost::beast; namespace http = boost::beast::http; +namespace ip = asio::ip; using tcp = boost::asio::ip::tcp; // main @@ -70,7 +71,7 @@ class Client beast::error_code ec; // These objects perform our I/O - tcp::endpoint server(asio::ip::address_v4::from_string("127.0.0.1"), port); + tcp::endpoint server(ip::make_address("127.0.0.1"), port); // Set the timeout. m_stream.expires_after(std::chrono::seconds(30)); @@ -231,7 +232,8 @@ class Client void spawnReadChunk() { - asio::spawn(m_context, std::bind(&Client::readChunk, this, std::placeholders::_1)); + asio::spawn(m_context, std::bind(&Client::readChunk, this, std::placeholders::_1), + boost::asio::detached); } void spawnRequest(boost::beast::http::verb verb, std::string const& target, @@ -242,7 +244,8 @@ class Client m_done = false; m_count = 0; asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1)); + std::placeholders::_1), + boost::asio::detached); while (!m_done && m_context.run_for(20ms) > 0) ; @@ -307,7 +310,9 @@ class HttpServerTest : public testing::Test m_client->m_connected = false; asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), - static_cast(m_server->getPort()), std::placeholders::_1)); + static_cast(m_server->getPort()), std::placeholders::_1), + boost::asio::detached); + while (!m_client->m_connected) m_context.run_one(); diff --git a/test_package/mqtt_isolated_test.cpp b/test_package/mqtt_isolated_test.cpp index aecdf3b55..fb802105f 100644 --- a/test_package/mqtt_isolated_test.cpp +++ b/test_package/mqtt_isolated_test.cpp @@ -115,7 +115,7 @@ class MqttIsolatedUnitTest : public testing::Test bool waitFor(const chrono::duration &time, function pred) { boost::asio::steady_timer timer(m_agentTestHelper->m_ioContext); - timer.expires_from_now(time); + timer.expires_after(time); bool timeout = false; timer.async_wait([&timeout](boost::system::error_code ec) { if (!ec) diff --git a/test_package/period_filter_test.cpp b/test_package/period_filter_test.cpp index f1d39ecc5..4e2c01f21 100644 --- a/test_package/period_filter_test.cpp +++ b/test_package/period_filter_test.cpp @@ -607,7 +607,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(400ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "2"}, now + 400ms); @@ -617,7 +617,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(200ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "3"}, now + 600ms); @@ -627,7 +627,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(600ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "4"}, now + 1200ms); @@ -638,7 +638,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(700ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "5"}, now + 1900ms); @@ -648,7 +648,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(1200ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "6"}, now + 3100ms); @@ -660,7 +660,7 @@ TEST_F(PeriodFilterTest, streaming_observations_spaced_temporally) } m_ioContext.run_for(1400ms); - m_ioContext.reset(); + m_ioContext.restart(); { auto os = observe({"a", "7"}, now + 4500ms); diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index aa2f560bc..4ae945163 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -75,7 +75,7 @@ class Client SSL_set_tlsext_host_name(m_stream.native_handle(), "localhost"); // These objects perform our I/O - tcp::endpoint server(asio::ip::address_v4::from_string("127.0.0.1"), port); + tcp::endpoint server(asio::ip::make_address("127.0.0.1"), port); // Set the timeout. beast::get_lowest_layer(m_stream).expires_after(std::chrono::seconds(30)); @@ -252,7 +252,7 @@ class Client m_done = false; m_count = 0; asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1)); + std::placeholders::_1), boost::asio::detached); while (!m_done && !m_failed && m_context.run_for(20ms) > 0) ; @@ -271,7 +271,7 @@ class Client m_done = true; }; - asio::spawn(m_context, std::bind(closeStream, std::placeholders::_1)); + asio::spawn(m_context, std::bind(closeStream, std::placeholders::_1), boost::asio::detached); while (!m_done && m_context.run_for(100ms) > 0) ; } @@ -362,7 +362,8 @@ class TlsRestServiceTest : public testing::Test m_client->m_clientCert = clientCert; asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), - static_cast(m_server->getPort()), std::placeholders::_1)); + static_cast(m_server->getPort()), std::placeholders::_1), + boost::asio::detached); while (!m_client->m_connected && !m_client->m_failed) m_context.run_one(); diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index 856c2f147..a710c73d8 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -73,7 +73,7 @@ class Client beast::error_code ec; // These objects perform our I/O - tcp::endpoint server(asio::ip::address_v4::from_string("127.0.0.1"), port); + tcp::endpoint server(asio::ip::make_address("127.0.0.1"), port); // Make the connection on the IP address we get from a lookup beast::get_lowest_layer(m_stream).async_connect(server, yield[ec]); @@ -126,7 +126,7 @@ class Client bool waitFor(const chrono::duration& time, function pred) { boost::asio::steady_timer timer(m_context); - timer.expires_from_now(time); + timer.expires_after(time); bool timeout = false; timer.async_wait([&timeout](boost::system::error_code ec) { if (!ec) @@ -197,7 +197,8 @@ class WebsocketsTest : public testing::Test m_client->m_connected = false; asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), - static_cast(m_server->getPort()), std::placeholders::_1)); + static_cast(m_server->getPort()), std::placeholders::_1), + boost::asio::detached); m_client->waitFor(1s, [this]() { return m_client->m_connected; }); } @@ -241,7 +242,8 @@ TEST_F(WebsocketsTest, should_make_simple_request) startClient(); asio::spawn(m_context, std::bind(&Client::request, m_client.get(), - "{\"id\":\"1\",\"request\":\"probe\"}"s, std::placeholders::_1)); + "{\"id\":\"1\",\"request\":\"probe\"}"s, std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -269,7 +271,8 @@ TEST_F(WebsocketsTest, should_return_error_when_there_is_no_id) startClient(); asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, - std::placeholders::_1)); + std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -297,7 +300,8 @@ TEST_F(WebsocketsTest, should_return_error_when_there_is_no_request) startClient(); asio::spawn(m_context, - std::bind(&Client::request, m_client.get(), "{\"id\": 3}"s, std::placeholders::_1)); + std::bind(&Client::request, m_client.get(), "{\"id\": 3}"s, std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -358,7 +362,8 @@ TEST_F(WebsocketsTest, should_return_error_when_bad_json_is_sent) startClient(); asio::spawn(m_context, - std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1)); + std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -392,7 +397,8 @@ TEST_F(WebsocketsTest, should_return_multiple_errors_when_parameters_are_invalid m_context, std::bind(&Client::request, m_client.get(), R"DOC({"id": 3, "request": "sample", "interval": 99999999999,"to": -1 })DOC", - std::placeholders::_1)); + std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -424,7 +430,8 @@ TEST_F(WebsocketsTest, should_return_error_for_an_invalid_command) asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"id\":\"1\",\"request\":\"sample\"}"s, - std::placeholders::_1)); + std::placeholders::_1), + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); From 4913a740cb02f15f6c5985849b97537e73fe457c Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 24 Sep 2025 18:38:19 +0200 Subject: [PATCH 10/84] Fixed mqtt adapter ids --- src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp | 3 ++- src/mtconnect/utilities.hpp | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index fbbaf873d..82533ae81 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -166,7 +166,8 @@ namespace mtconnect { identity << s; } - m_options[configuration::AdapterIdentity] = CreateIdentityHash(identity.str()); + m_identity = CreateIdentityHash(identity.str()); + m_options[configuration::AdapterIdentity] = m_identity; } m_pipeline.build(m_options); diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index bd7efff08..6f468b3d7 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -814,11 +814,12 @@ namespace mtconnect { /// @param[in] sha the sha1 namespace to use as context /// @param[in] id the id to use transform /// @returns Returns the first 16 characters of the base 64 encoded sha1 - inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &sha, const std::string &id) + inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, const std::string &id) { using namespace std; + using namespace boost::uuids::detail; - ::boost::uuids::detail::sha1 sha1(sha); + sha1 sha(contextSha); constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"); constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; }; @@ -826,9 +827,9 @@ namespace mtconnect { return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-'; }; - sha1.process_bytes(id.data(), id.length()); - unsigned char digest[20]; - sha1.get_digest(digest); + sha.process_bytes(id.data(), id.length()); + sha1::digest_type digest; + sha.get_digest(digest); string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), digest, sizeof(digest)); @@ -845,7 +846,7 @@ namespace mtconnect { s[0] = startc[c % startc.size()]; } - s.erase(16); + s.erase(16); //DFYX7ls4d4to2Lhb return s; } From 47046e1f22dd22cc27e0b5b9664d05303b2d7605 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 25 Sep 2025 09:26:26 +0200 Subject: [PATCH 11/84] Porting to C++ 20 now complete, all tests pass on OS X --- conanfile.py | 2 +- src/mtconnect/utilities.hpp | 6 ++++-- test_package/entity_printer_test.cpp | 16 +++++++-------- test_package/json_printer_test.cpp | 28 +++++++++++++-------------- test_package/tls_http_server_test.cpp | 15 +++++++------- test_package/websockets_test.cpp | 3 ++- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/conanfile.py b/conanfile.py index 85314cc57..6f6d7a0dc 100644 --- a/conanfile.py +++ b/conanfile.py @@ -124,7 +124,7 @@ def requirements(self): self.requires("nlohmann_json/3.9.1", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("openssl/3.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("rapidjson/cci.20220822", headers=True, libs=False, transitive_headers=True, transitive_libs=False) - self.requires("mqtt_cpp/13.2.1", headers=True, libs=False, transitive_headers=True, transitive_libs=False) + self.requires("mqtt_cpp/13.2.2", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("bzip2/1.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) if self.options.with_ruby: diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 6f468b3d7..ef21c00e4 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -831,8 +831,10 @@ namespace mtconnect { sha1::digest_type digest; sha.get_digest(digest); + auto data = (unsigned int *) digest; + string s(32, ' '); - auto len = boost::beast::detail::base64::encode(s.data(), digest, sizeof(digest)); + auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); s.erase(len - 1); s.erase(std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar)), s.end()); @@ -846,7 +848,7 @@ namespace mtconnect { s[0] = startc[c % startc.size()]; } - s.erase(16); //DFYX7ls4d4to2Lhb + s.erase(16); return s; } diff --git a/test_package/entity_printer_test.cpp b/test_package/entity_printer_test.cpp index 43a7fe29f..b3fa98ac9 100644 --- a/test_package/entity_printer_test.cpp +++ b/test_package/entity_printer_test.cpp @@ -331,12 +331,12 @@ TEST_F(EntityPrinterTest, should_honor_include_hidden_parameter) entity::XmlPrinter printer(false); printer.print(*m_writer, entity, {}); - ASSERT_EQ(R"( + ASSERT_EQ(R"( - + - - + + @@ -348,12 +348,12 @@ TEST_F(EntityPrinterTest, should_honor_include_hidden_parameter) entity::XmlPrinter printer2(true); printer2.print(*m_writer, entity, {}); - ASSERT_EQ(R"( + ASSERT_EQ(R"( - + - - + + diff --git a/test_package/json_printer_test.cpp b/test_package/json_printer_test.cpp index 157c9274e..0405d6ea2 100644 --- a/test_package/json_printer_test.cpp +++ b/test_package/json_printer_test.cpp @@ -392,12 +392,12 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) "Components": [ { "Electric": { - "id": "Pm2JhGKEeAYzVA8c" + "id": "hIltPgZ4hGIcD1Qz" } }, { "Heating": { - "id": "culKrBObwYWb6x0g" + "id": "rErpcoXBmxMgHeub" } } ], @@ -405,7 +405,7 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) "value": "Hey Will", "model": "abc" }, - "id": "_cNZEyq5kGkgppmh" + "id": "ZA2H50HmqkxmmoKk" } } ], @@ -413,7 +413,7 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) { "DataItem": { "category": "EVENT", - "id": "FFZeJQRwQvAdUJX4", + "id": "JV5WFPBCcAT4lVAd", "name": "avail", "type": "AVAILABILITY" } @@ -421,19 +421,19 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) { "DataItem": { "category": "EVENT", - "id": "T0qItk3igtyip1XX", + "id": "tohKT9C4k1nYryEA", "type": "ASSET_CHANGED" } }, { "DataItem": { "category": "EVENT", - "id": "LWOt9yZtpFPWjL7v", + "id": "F1jLVOkbSb7Mv7WI", "type": "ASSET_REMOVED" } } ], - "id": "DFYX7ls4d4to2Lhb", + "id": "AdWDIt3OFtbuNhoe", "name": "foo", "uuid": "xxx" } @@ -467,13 +467,13 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) "Components": [ { "Electric": { - "id": "Pm2JhGKEeAYzVA8c", + "id": "hIltPgZ4hGIcD1Qz", "originalId": "e1" } }, { "Heating": { - "id": "culKrBObwYWb6x0g", + "id": "rErpcoXBmxMgHeub", "originalId": "h1" } } @@ -482,7 +482,7 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) "value": "Hey Will", "model": "abc" }, - "id": "_cNZEyq5kGkgppmh", + "id": "ZA2H50HmqkxmmoKk", "originalId": "s1" } } @@ -491,7 +491,7 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) { "DataItem": { "category": "EVENT", - "id": "FFZeJQRwQvAdUJX4", + "id": "JV5WFPBCcAT4lVAd", "name": "avail", "originalId": "avail", "type": "AVAILABILITY" @@ -500,7 +500,7 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) { "DataItem": { "category": "EVENT", - "id": "T0qItk3igtyip1XX", + "id": "tohKT9C4k1nYryEA", "originalId": "d1_asset_chg", "type": "ASSET_CHANGED" } @@ -508,13 +508,13 @@ TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) { "DataItem": { "category": "EVENT", - "id": "LWOt9yZtpFPWjL7v", + "id": "F1jLVOkbSb7Mv7WI", "originalId": "d1_asset_rem", "type": "ASSET_REMOVED" } } ], - "id": "DFYX7ls4d4to2Lhb", + "id": "AdWDIt3OFtbuNhoe", "name": "foo", "originalId": "d1", "uuid": "xxx" diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index 4ae945163..13a047719 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -241,7 +241,8 @@ class Client void spawnReadChunk() { - asio::spawn(m_context, std::bind(&Client::readChunk, this, std::placeholders::_1)); + asio::spawn(m_context, std::bind(&Client::readChunk, this, std::placeholders::_1), + asio::detached); } void spawnRequest(boost::beast::http::verb verb, std::string const& target, @@ -309,7 +310,7 @@ const string ClientKeyFile {TEST_RESOURCE_DIR "/client.key"}; const string ClientDhFile {TEST_RESOURCE_DIR "/dh2048.pem"}; const string ClientCAFile(TEST_RESOURCE_DIR "/clientca.crt"); -class TlsRestServiceTest : public testing::Test +class TlsHttpServerTest : public testing::Test { protected: void SetUp() override @@ -385,7 +386,7 @@ class TlsRestServiceTest : public testing::Test unique_ptr m_client; }; -TEST_F(TlsRestServiceTest, create_server_and_load_certificates) +TEST_F(TlsHttpServerTest, create_server_and_load_certificates) { weak_ptr savedSession; @@ -423,7 +424,7 @@ TEST_F(TlsRestServiceTest, create_server_and_load_certificates) ASSERT_TRUE(savedSession.expired()); } -TEST_F(TlsRestServiceTest, streaming_response) +TEST_F(TlsHttpServerTest, streaming_response) { struct context { @@ -506,7 +507,7 @@ TEST_F(TlsRestServiceTest, streaming_response) ; } -TEST_F(TlsRestServiceTest, check_failed_client_certificate) +TEST_F(TlsHttpServerTest, check_failed_client_certificate) { using namespace mtconnect::configuration; ConfigOptions options {{TlsCertificateChain, CertFile}, @@ -534,7 +535,7 @@ TEST_F(TlsRestServiceTest, check_failed_client_certificate) const string ClientCA(TEST_RESOURCE_DIR "/clientca.crt"); -TEST_F(TlsRestServiceTest, check_valid_client_certificate) +TEST_F(TlsHttpServerTest, check_valid_client_certificate) { using namespace mtconnect::configuration; ConfigOptions options {{TlsCertificateChain, CertFile}, @@ -565,7 +566,7 @@ TEST_F(TlsRestServiceTest, check_valid_client_certificate) EXPECT_EQ(200, m_client->m_status); } -TEST_F(TlsRestServiceTest, check_valid_client_certificate_without_server_ca) +TEST_F(TlsHttpServerTest, check_valid_client_certificate_without_server_ca) { using namespace mtconnect::configuration; ConfigOptions options {{TlsCertificateChain, CertFile}, diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index a710c73d8..07044401f 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -332,7 +332,8 @@ TEST_F(WebsocketsTest, should_return_error_when_a_parameter_is_invalid) asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"id\": 3, \"request\": \"sample\", \"interval\": 99999999999}"s, - std::placeholders::_1)); + std::placeholders::_1), + asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); From 2f7cf10fb79d9fbf1aa72ebe1c49ee2656d30abc Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 25 Sep 2025 10:15:44 +0200 Subject: [PATCH 12/84] Added forward --- src/mtconnect/entity/data_set.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtconnect/entity/data_set.hpp b/src/mtconnect/entity/data_set.hpp index e6dfc66c2..671768b19 100644 --- a/src/mtconnect/entity/data_set.hpp +++ b/src/mtconnect/entity/data_set.hpp @@ -83,7 +83,7 @@ namespace mtconnect::entity { /// @param value the a data set variant /// @param removed `true` if the key has been removed Entry(const std::string &key, T value, bool removed = false) - : m_key(key), m_value(std::move(value)), m_removed(removed) + : m_key(key), m_value(std::forward(value)), m_removed(removed) {} /// @brief Create a data set entry with just a key (used for search) /// @param key From 28a225cbeedf4c8b9c8dff459db1e76acc52dc28 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 25 Sep 2025 16:50:52 +0200 Subject: [PATCH 13/84] Fixed get local time on windows. --- src/mtconnect/pipeline/shdr_tokenizer.hpp | 2 +- src/mtconnect/utilities.hpp | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mtconnect/pipeline/shdr_tokenizer.hpp b/src/mtconnect/pipeline/shdr_tokenizer.hpp index d53353cae..e8709dcb6 100644 --- a/src/mtconnect/pipeline/shdr_tokenizer.hpp +++ b/src/mtconnect/pipeline/shdr_tokenizer.hpp @@ -34,7 +34,7 @@ namespace mtconnect::pipeline { using entity::Entity::Entity; Tokens(const Tokens &) = default; Tokens() = default; - Tokens(const Tokens &ts, TokenList list) : Entity(ts), m_tokens(list) {} + Tokens(const Tokens &ts, const TokenList &list) : Entity(ts), m_tokens(list) {} TokenList m_tokens; }; diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index ef21c00e4..70bddd58f 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -219,7 +219,12 @@ namespace mtconnect { using namespace std; using namespace std::chrono; constexpr char ISO_8601_FMT[] = "%Y-%m-%dT%H:%M:%SZ"; - +#ifdef _WINDOWS + namespace tzchrono = std::chrono; +#else + namespace tzchrono = date; +#endif + switch (format) { case HUM_READ: @@ -230,7 +235,7 @@ namespace mtconnect { return date::format(ISO_8601_FMT, date::floor(timePoint)); case LOCAL: { - auto zone = date::current_zone(); + auto zone = tzchrono::current_zone(); auto zt = date::zoned_time(zone, timePoint); return date::format("%Y-%m-%dT%H:%M:%S%z", zt); } From 6608baef7282254d18dcf3a8d73c27475298c409 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 28 Sep 2025 22:13:24 +0200 Subject: [PATCH 14/84] Version 2.6.0.3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e44023a8..d1220305c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 2) +set(AGENT_VERSION_BUILD 3) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From 53a9ad93adf12ed9b310ca3c9447359654ba72dd Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 1 Oct 2025 17:08:56 +0200 Subject: [PATCH 15/84] upgrading to windows latest --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b743fe8e2..f1e5b5e9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,8 +28,8 @@ on: jobs: build_windows: - runs-on: windows-2022 - name: "Windows 2022 Arch: ${{ matrix.arch }}, Shared: ${{ matrix.shared }}" + runs-on: windows-latest + name: "Windows latest Arch: ${{ matrix.arch }}, Shared: ${{ matrix.shared }}" strategy: matrix: arch: ["x86", "amd64"] From 6f2adaddf8248864a4ae04840880c596688590ff Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 1 Oct 2025 17:21:34 +0200 Subject: [PATCH 16/84] removed x86 build to see if that was an issue --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1e5b5e9f..a1354ceea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: name: "Windows latest Arch: ${{ matrix.arch }}, Shared: ${{ matrix.shared }}" strategy: matrix: - arch: ["x86", "amd64"] + arch: ["amd64"] # ["x86", "amd64"] shared: ["True", "False"] include: - arch: "x86" From ec7b3caac2610557d7fe0c58aee88861eb4ca823 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 1 Oct 2025 17:23:12 +0200 Subject: [PATCH 17/84] removed x86 build to see if that was an issue --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1354ceea..79bddec19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,8 +35,8 @@ jobs: arch: ["amd64"] # ["x86", "amd64"] shared: ["True", "False"] include: - - arch: "x86" - profile: "vs32" + #- arch: "x86" + # profile: "vs32" - arch: "amd64" profile: "vs64" - shared: "True" From 226985e7d8f24448b02787c092623dd84677f4b6 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 1 Oct 2025 19:57:20 +0200 Subject: [PATCH 18/84] Added additional -d+2 for windows build for boost b2 options --- .github/workflows/build.yml | 6 +++--- conanfile.py | 10 ++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79bddec19..f1e5b5e9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,11 +32,11 @@ jobs: name: "Windows latest Arch: ${{ matrix.arch }}, Shared: ${{ matrix.shared }}" strategy: matrix: - arch: ["amd64"] # ["x86", "amd64"] + arch: ["x86", "amd64"] shared: ["True", "False"] include: - #- arch: "x86" - # profile: "vs32" + - arch: "x86" + profile: "vs32" - arch: "amd64" profile: "vs64" - shared: "True" diff --git a/conanfile.py b/conanfile.py index 6f6d7a0dc..6e213a67d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -84,10 +84,6 @@ def layout(self): self.folders.build_folder_vars = ["options.shared", "settings.arch"] cmake_layout(self) - def layout(self): - self.folders.build_folder_vars = ["options.shared", "settings.arch"] - cmake_layout(self) - def config_options(self): if is_msvc(self): self.options.rm_safe("fPIC") @@ -105,6 +101,7 @@ def tool_requires_version(self, package, version): self.output.info(f"Version of {package} is {ver}") else: self.output.info(f"Command: '{command}' returned {res}") + if ver < version: ver_text = '.'.join([str(x) for x in version]) self.output.info(f"Old version of {package}, requesting tool {package}/{ver_text}") @@ -141,10 +138,7 @@ def configure(self): self.package_type = "shared-library" if is_msvc(self): - self.options["boost/*"].extra_b2_flags = ("define=BOOST_USE_WINAPI_VERSION=" + str(self.options.winver)) - - if is_msvc(self): - self.options["boost/*"].extra_b2_flags = ("define=BOOST_USE_WINAPI_VERSION=" + str(self.options.winver)) + self.options["boost/*"].extra_b2_flags = ("define=BOOST_USE_WINAPI_VERSION=" + str(self.options.winver) + " -d+2") # Make sure shared builds use shared boost if is_msvc(self) and self.options.shared: From dfc03586a24a7f9d9e4d34a4e74364e0d41c171a Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 3 Oct 2025 11:37:13 +0200 Subject: [PATCH 19/84] Set windows build target to 0x0A00 (Windows 10) --- conan/profiles/vs32 | 2 -- conan/profiles/vs32debug | 3 --- conan/profiles/vs32shared | 4 ---- conan/profiles/vsxp | 15 --------------- conanfile.py | 12 ++++++------ 5 files changed, 6 insertions(+), 30 deletions(-) delete mode 100644 conan/profiles/vsxp diff --git a/conan/profiles/vs32 b/conan/profiles/vs32 index 423cc9523..3a5f87099 100644 --- a/conan/profiles/vs32 +++ b/conan/profiles/vs32 @@ -8,5 +8,3 @@ compiler.runtime=static compiler.runtime_type=Release build_type=Release -[options] -winver=0x0600 diff --git a/conan/profiles/vs32debug b/conan/profiles/vs32debug index 06f2fb2c9..c88c703e0 100644 --- a/conan/profiles/vs32debug +++ b/conan/profiles/vs32debug @@ -7,6 +7,3 @@ arch=x86 compiler.runtime=static compiler.runtime_type=Debug build_type=Debug - -[options] -winver=0x0600 diff --git a/conan/profiles/vs32shared b/conan/profiles/vs32shared index ca9b3fa6d..a06bc0799 100644 --- a/conan/profiles/vs32shared +++ b/conan/profiles/vs32shared @@ -8,7 +8,3 @@ compiler.runtime=dynamic compiler.runtime_type=Release build_type=Release - -[options] -shared=True -winver=0x0600 diff --git a/conan/profiles/vsxp b/conan/profiles/vsxp deleted file mode 100644 index 1acbe97d5..000000000 --- a/conan/profiles/vsxp +++ /dev/null @@ -1,15 +0,0 @@ -include(default) - -[settings] -compiler=msvc -arch=x86 -compiler.cppstd=20 -compiler.runtime=static -compiler.runtime_type=Release -build_type=Release -compiler.toolset=v141_xp - -[options] -winver=0x0501 -with_ruby=False -date:header_only=True diff --git a/conanfile.py b/conanfile.py index 6e213a67d..4d7d044e8 100644 --- a/conanfile.py +++ b/conanfile.py @@ -35,7 +35,7 @@ class MTConnectAgentConan(ConanFile): "with_ruby": True, "development": False, "shared": False, - "winver": "0x0602", + "winver": "0x0A00", "with_docs": False, "cpack": False, "agent_prefix": None, @@ -116,11 +116,11 @@ def build_requirements(self): def requirements(self): self.requires("boost/1.88.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) - self.requires("libxml2/2.10.3", headers=True, libs=True, visible=True, transitive_headers=True, transitive_libs=True) + self.requires("libxml2/2.14.5", headers=True, libs=True, visible=True, transitive_headers=True, transitive_libs=True) self.requires("date/3.0.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) - self.requires("nlohmann_json/3.9.1", headers=True, libs=False, transitive_headers=True, transitive_libs=False) - self.requires("openssl/3.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) - self.requires("rapidjson/cci.20220822", headers=True, libs=False, transitive_headers=True, transitive_libs=False) + self.requires("nlohmann_json/3.12.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) + self.requires("openssl/3.5.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) + self.requires("rapidjson/1.1.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("mqtt_cpp/13.2.2", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("bzip2/1.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) @@ -138,7 +138,7 @@ def configure(self): self.package_type = "shared-library" if is_msvc(self): - self.options["boost/*"].extra_b2_flags = ("define=BOOST_USE_WINAPI_VERSION=" + str(self.options.winver) + " -d+2") + self.options["boost/*"].extra_b2_flags = ("define=BOOST_USE_WINAPI_VERSION=" + str(self.options.winver)) # Make sure shared builds use shared boost if is_msvc(self) and self.options.shared: From d9812e9e5f7c8f0a564191a6f4b3d022982eb58c Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 3 Oct 2025 18:48:15 +0200 Subject: [PATCH 20/84] Upgraded to the latest version of libraries --- conanfile.py | 2 +- src/mtconnect/entity/json_printer.hpp | 4 ++-- src/mtconnect/entity/xml_parser.cpp | 2 +- src/mtconnect/entity/xml_printer.cpp | 1 + src/mtconnect/parser/xml_parser.cpp | 2 +- src/mtconnect/printer/xml_printer.cpp | 1 + test_package/json_printer_probe_test.cpp | 16 ++++++++-------- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/conanfile.py b/conanfile.py index 4d7d044e8..94213e50e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -120,7 +120,7 @@ def requirements(self): self.requires("date/3.0.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("nlohmann_json/3.12.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("openssl/3.5.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) - self.requires("rapidjson/1.1.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) + self.requires("rapidjson/cci.20230929", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("mqtt_cpp/13.2.2", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("bzip2/1.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) diff --git a/src/mtconnect/entity/json_printer.hpp b/src/mtconnect/entity/json_printer.hpp index 12cea9ca0..5154ed33a 100644 --- a/src/mtconnect/entity/json_printer.hpp +++ b/src/mtconnect/entity/json_printer.hpp @@ -306,7 +306,7 @@ namespace mtconnect::entity { printer.printEntity(entity); }); - return std::string(output.GetString(), output.GetLength()); + return std::string(output.GetString(), output.GetSize()); } /// @brief wrapper around the JsonPrinter print method that creates the correct printer @@ -325,7 +325,7 @@ namespace mtconnect::entity { printer.print(entity); }); - return std::string(output.GetString(), output.GetLength()); + return std::string(output.GetString(), output.GetSize()); } protected: diff --git a/src/mtconnect/entity/xml_parser.cpp b/src/mtconnect/entity/xml_parser.cpp index c509e8995..0a88a6269 100644 --- a/src/mtconnect/entity/xml_parser.cpp +++ b/src/mtconnect/entity/xml_parser.cpp @@ -32,7 +32,7 @@ using namespace std; namespace mtconnect::entity { using namespace mtconnect::printer; - extern "C" void XMLCDECL entityXMLErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) + extern "C" void XMLCDECL entityXMLErrorFunc([[maybe_unused]] void *ctx, const char *msg, ...) { va_list args; diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp index ce5a4122c..3cfb0ccb4 100644 --- a/src/mtconnect/entity/xml_printer.cpp +++ b/src/mtconnect/entity/xml_printer.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "mtconnect/logging.hpp" #include "mtconnect/printer/xml_printer_helper.hpp" diff --git a/src/mtconnect/parser/xml_parser.cpp b/src/mtconnect/parser/xml_parser.cpp index fe41b4aa7..9f5363e75 100644 --- a/src/mtconnect/parser/xml_parser.cpp +++ b/src/mtconnect/parser/xml_parser.cpp @@ -62,7 +62,7 @@ namespace mtconnect::parser { using namespace device_model; using namespace printer; - extern "C" void XMLCDECL agentXMLErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) + extern "C" void XMLCDECL agentXMLErrorFunc([[maybe_unused]] void *ctx, const char *msg, ...) { va_list args; diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 2697cd680..3ea09f277 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -25,6 +25,7 @@ #include #include +#include #include "mtconnect/asset/asset.hpp" #include "mtconnect/asset/cutting_tool.hpp" diff --git a/test_package/json_printer_probe_test.cpp b/test_package/json_printer_probe_test.cpp index 978336217..1c2a1cb65 100644 --- a/test_package/json_printer_probe_test.cpp +++ b/test_package/json_printer_probe_test.cpp @@ -355,14 +355,14 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir1 = load.at("/Relationships/0"_json_pointer); ASSERT_TRUE(dir1.is_object()); - ASSERT_EQ(string("archie"), dir1.at("/DataItemRelationship/name"_json_pointer)); - ASSERT_EQ(string("LIMIT"), dir1.at("/DataItemRelationship/type"_json_pointer)); - ASSERT_EQ(string("xlcpl"), dir1.at("/DataItemRelationship/idRef"_json_pointer)); + ASSERT_EQ(string("archie"), dir1.at("/DataItemRelationship/name"_json_pointer).get()); + ASSERT_EQ(string("LIMIT"), dir1.at("/DataItemRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("xlcpl"), dir1.at("/DataItemRelationship/idRef"_json_pointer).get()); auto dir2 = load.at("/Relationships/1"_json_pointer); ASSERT_TRUE(dir2.is_object()); - ASSERT_EQ(string("LIMIT"), dir2.at("/SpecificationRelationship/type"_json_pointer)); - ASSERT_EQ(string("spec1"), dir2.at("/SpecificationRelationship/idRef"_json_pointer)); + ASSERT_EQ(string("LIMIT"), dir2.at("/SpecificationRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("spec1"), dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); auto limits = linear.at("/DataItems/5/DataItem"_json_pointer); ASSERT_TRUE(load.is_object()); @@ -370,9 +370,9 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir3 = limits.at("/Relationships/0"_json_pointer); ASSERT_TRUE(dir3.is_object()); - ASSERT_EQ(string("bob"), dir3.at("/DataItemRelationship/name"_json_pointer)); - ASSERT_EQ(string("OBSERVATION"), dir3.at("/DataItemRelationship/type"_json_pointer)); - ASSERT_EQ(string("xlc"), dir3.at("/DataItemRelationship/idRef"_json_pointer)); + ASSERT_EQ(string("bob"), dir3.at("/DataItemRelationship/name"_json_pointer).get()); + ASSERT_EQ(string("OBSERVATION"), dir3.at("/DataItemRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("xlc"), dir3.at("/DataItemRelationship/idRef"_json_pointer).get()); } TEST_F(JsonPrinterProbeTest, version_2_with_multiple_devices) From 25df43a8eb501df2ad2f477dec95426a3c4e30a6 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 4 Oct 2025 14:35:18 +0200 Subject: [PATCH 21/84] Reverted json api back in entity to v2.6.0.2 --- src/mtconnect/entity/json_printer.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mtconnect/entity/json_printer.hpp b/src/mtconnect/entity/json_printer.hpp index 5154ed33a..47e64f648 100644 --- a/src/mtconnect/entity/json_printer.hpp +++ b/src/mtconnect/entity/json_printer.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -306,7 +306,7 @@ namespace mtconnect::entity { printer.printEntity(entity); }); - return std::string(output.GetString(), output.GetSize()); + return std::string(output.GetString(), output.GetLength()); } /// @brief wrapper around the JsonPrinter print method that creates the correct printer @@ -325,7 +325,7 @@ namespace mtconnect::entity { printer.print(entity); }); - return std::string(output.GetString(), output.GetSize()); + return std::string(output.GetString(), output.GetLength()); } protected: From 4f2958568a25eb4ded02cc367491b44d73a11977 Mon Sep 17 00:00:00 2001 From: virajdere Date: Thu, 23 Oct 2025 19:02:45 +0000 Subject: [PATCH 22/84] added feature that publishes observations in a per-data-item topic structure --- agent_lib/CMakeLists.txt | 16 + src/mtconnect/configuration/agent_config.cpp | 2 + .../configuration/config_options.hpp | 3 + .../mqtt_entity_sink/mqtt_entity_sink.cpp | 525 ++++++++++++++++++ .../mqtt_entity_sink/mqtt_entity_sink.hpp | 121 ++++ 5 files changed, 667 insertions(+) create mode 100644 src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp create mode 100644 src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 37cfdef25..343a2c1c8 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -256,6 +256,22 @@ set(AGENT_SOURCES #src/sink/mqtt_sink SOURCE_FILES_ONLY "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.cpp" + +# src/sink/mqtt_sink HEADER_FILE_ONLY + + "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.hpp" + +#src/sink/mqtt_sink SOURCE_FILES_ONLY + + "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.cpp" + +# src/sink/mqtt_entity_sink HEADER_FILE_ONLY + + "${SOURCE_DIR}/sink/mqtt_entity_sink/mqtt_entity_sink.hpp" + +#src/sink/mqtt_entity_sink SOURCE_FILES_ONLY + + "${SOURCE_DIR}/sink/mqtt_entity_sink/mqtt_entity_sink.cpp" # src/sink/rest_sink HEADER_FILE_ONLY diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 48ee6f8b2..90941458a 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -59,6 +59,7 @@ #include "mtconnect/device_model/device.hpp" #include "mtconnect/printer/xml_printer.hpp" #include "mtconnect/sink/mqtt_sink/mqtt_service.hpp" +#include "mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp" #include "mtconnect/sink/rest_sink/rest_service.hpp" #include "mtconnect/source/adapter/agent_adapter/agent_adapter.hpp" #include "mtconnect/source/adapter/mqtt/mqtt_adapter.hpp" @@ -111,6 +112,7 @@ namespace mtconnect::configuration { sink::mqtt_sink::MqttService::registerFactory(m_sinkFactory); sink::rest_sink::RestService::registerFactory(m_sinkFactory); + sink::mqtt_entity_sink::MqttEntitySink::registerFactory(m_sinkFactory); adapter::shdr::ShdrAdapter::registerFactory(m_sourceFactory); adapter::mqtt_adapter::MqttAdapter::registerFactory(m_sourceFactory); adapter::agent_adapter::AgentAdapter::registerFactory(m_sourceFactory); diff --git a/src/mtconnect/configuration/config_options.hpp b/src/mtconnect/configuration/config_options.hpp index b192c70d4..e2ac36aec 100644 --- a/src/mtconnect/configuration/config_options.hpp +++ b/src/mtconnect/configuration/config_options.hpp @@ -107,6 +107,9 @@ namespace mtconnect { DECLARE_CONFIGURATION(MqttMaxTopicDepth); DECLARE_CONFIGURATION(MqttLastWillTopic); DECLARE_CONFIGURATION(MqttXPath); + DECLARE_CONFIGURATION(ObservationTopicPrefix); + DECLARE_CONFIGURATION(DeviceTopicPrefix); + DECLARE_CONFIGURATION(AssetTopicPrefix); ///@} /// @name Adapter Configuration diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp new file mode 100644 index 000000000..488e50ba8 --- /dev/null +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp @@ -0,0 +1,525 @@ +#include "mqtt_entity_sink.hpp" + +#include +#include + +#include + +#include "mtconnect/configuration/config_options.hpp" +#include "mtconnect/entity/entity.hpp" +#include "mtconnect/mqtt/mqtt_client_impl.hpp" +#include "mtconnect/observation/observation.hpp" + +using ptree = boost::property_tree::ptree; +using json = nlohmann::json; + +using namespace std; +using namespace mtconnect; +using namespace mtconnect::asset; +using namespace mtconnect::observation; + +namespace asio = boost::asio; +namespace config = ::mtconnect::configuration; + +namespace mtconnect { + namespace sink { + namespace mqtt_entity_sink { + + MqttEntitySink::MqttEntitySink(boost::asio::io_context& context, + sink::SinkContractPtr&& contract, const ConfigOptions& options, + const ptree& config) + : Sink("MqttEntitySink", std::move(contract)), + m_context(context), + m_strand(context), + m_options(options) + { + GetOptions(config, m_options, options); + + AddOptions(config, m_options, + {{configuration::MqttCaCert, string()}, + {configuration::MqttPrivateKey, string()}, + {configuration::MqttCert, string()}, + {configuration::MqttClientId, string()}, + {configuration::MqttUserName, string()}, + {configuration::MqttPassword, string()}}); + + AddDefaultedOptions( + config, m_options, + {{configuration::MqttHost, "127.0.0.1"s}, + {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, + {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, + {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, + {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, + {configuration::MqttPort, 1883}, + {configuration::MqttTls, false}, + {configuration::MqttQOS, 1}, + {configuration::MqttRetain, false}}); + + m_observationTopicPrefix = get(m_options[configuration::ObservationTopicPrefix]); + m_deviceTopicPrefix = get(m_options[configuration::DeviceTopicPrefix]); + m_assetTopicPrefix = get(m_options[configuration::AssetTopicPrefix]); + } + + void MqttEntitySink::start() + { + if (!m_client) + { + auto clientHandler = make_unique(); + clientHandler->m_connected = [this](shared_ptr client) { + LOG(debug) << "MqttEntitySink: Client connected to broker"; + client->connectComplete(); + + auto agentDevice = m_sinkContract->getDeviceByName("Agent"); + if (agentDevice) + { + auto lwtTopic = get(m_options[configuration::MqttLastWillTopic]); + boost::replace_all(lwtTopic, "[device]", *agentDevice->getUuid()); + + // Get QoS and Retain from options + auto qos = static_cast( + GetOption(m_options, configuration::MqttQOS).value_or(1)); + bool retain = GetOption(m_options, configuration::MqttRetain).value_or(false); + LOG(debug) << "Publishing availability to: " << lwtTopic; + + client->publish(lwtTopic, "AVAILABLE", retain, qos); + } + + // Publish initial content + publishInitialContent(); + }; + + auto agentDevice = m_sinkContract->getDeviceByName("Agent"); + auto lwtTopic = get(m_options[configuration::MqttLastWillTopic]); + if (agentDevice) + { + boost::replace_all(lwtTopic, "[device]", *agentDevice->getUuid()); + } + m_lastWillTopic = lwtTopic; + + if (IsOptionSet(m_options, configuration::MqttTls)) + { + m_client = make_shared(m_context, m_options, std::move(clientHandler), + m_lastWillTopic, "UNAVAILABLE"s); + } + else + { + m_client = make_shared(m_context, m_options, std::move(clientHandler), + m_lastWillTopic, "UNAVAILABLE"s); + } + } + LOG(debug) << "Starting MQTT Entity Sink client"; + m_client->start(); + } + + void MqttEntitySink::stop() + { + if (m_client) + { + // Publish UNAVAILABLE before disconnecting + if (m_client->isConnected()) + { + auto qos = static_cast( + GetOption(m_options, configuration::MqttQOS).value_or(1)); + m_client->publish(m_lastWillTopic, "UNAVAILABLE", true, qos); + } + m_client->stop(); + } + } + + void MqttEntitySink::publishInitialContent() + { + LOG(debug) << "MqttEntitySink: Publishing initial content"; + + // Publish all devices + int deviceCount = 0; + for (auto& dev : m_sinkContract->getDevices()) + { + publish(dev); + deviceCount++; + } + LOG(debug) << "Published " << deviceCount << " devices"; + + // Publish current observations for all devices + int obsCount = 0; + for (auto& dev : m_sinkContract->getDevices()) + { + auto& buffer = m_sinkContract->getCircularBuffer(); + std::lock_guard lock(buffer); + + auto latest = buffer.getLatest(); + observation::ObservationList observations; + + for (auto& di : dev->getDeviceDataItems()) + { + auto dataItem = di.lock(); + if (dataItem) + { + auto obs = latest.getObservation(dataItem->getId()); + if (obs) + { + observations.push_back(obs); + } + } + } + + // Publish each observation + for (auto& obs : observations) + { + auto obsCopy = obs; + if (m_client && m_client->isConnected()) + { + publish(obsCopy); + obsCount++; + } + else + { + std::lock_guard lock(m_queueMutex); + if (m_queuedObservations.size() >= MAX_QUEUE_SIZE) + { + m_queuedObservations.erase(m_queuedObservations.begin()); + } + m_queuedObservations.push_back(obsCopy); + obsCount++; + } + } + } + LOG(debug) << "Published " << obsCount << " initial observations"; + } + + std::string MqttEntitySink::formatTimestamp(const Timestamp& timestamp) + { + using namespace date; + using namespace std::chrono; + + std::ostringstream oss; + oss << date::format("%FT%TZ", floor(timestamp)); + return oss.str(); + } + + std::string MqttEntitySink::getObservationValue( + const observation::ObservationPtr& observation) + { + if (observation->isUnavailable()) + { + return "UNAVAILABLE"; + } + + auto value = observation->getValue(); + + if (holds_alternative(value)) + { + return get(value); + } + else if (holds_alternative(value)) + { + return std::to_string(get(value)); + } + else if (holds_alternative(value)) + { + std::ostringstream oss; + oss << std::fixed << std::setprecision(6) << get(value); + return oss.str(); + } + else if (holds_alternative(value)) + { + auto& vec = get(value); + std::ostringstream oss; + for (size_t i = 0; i < vec.size(); ++i) + { + if (i > 0) + oss << " "; + oss << std::fixed << std::setprecision(6) << vec[i]; + } + return oss.str(); + } + else if (holds_alternative(value)) + { + // For DataSet, return as JSON string + json j = json::object(); + auto& ds = get(value); + for (auto& entry : ds) + { + // Convert the variant value to appropriate JSON type + if (holds_alternative(entry.m_value)) + { + j[entry.m_key] = get(entry.m_value); + } + else if (holds_alternative(entry.m_value)) + { + j[entry.m_key] = get(entry.m_value); + } + else if (holds_alternative(entry.m_value)) + { + j[entry.m_key] = get(entry.m_value); + } + } + return j.dump(); + } + + return "UNAVAILABLE"; + } + + std::string MqttEntitySink::formatObservationJson( + const observation::ObservationPtr& observation) + { + json j; + + try + { + auto dataItem = observation->getDataItem(); + if (!dataItem) + { + LOG(error) << "Observation has no data item"; + return "{}"; + } + + j["dataItemId"] = dataItem->getId(); + + const auto& name = dataItem->getName(); + if (name && !name->empty()) + { + j["name"] = *name; + } + + j["type"] = dataItem->getType(); + + auto subType = dataItem->maybeGet("subType"); + if (subType && !subType->empty()) + { + j["subType"] = *subType; + } + + j["timestamp"] = formatTimestamp(observation->getTimestamp()); + + // Get the category + auto category = dataItem->getCategory(); + if (category == device_model::data_item::DataItem::SAMPLE) + { + j["category"] = "SAMPLE"; + } + else if (category == device_model::data_item::DataItem::EVENT) + { + j["category"] = "EVENT"; + } + else if (category == device_model::data_item::DataItem::CONDITION) + { + j["category"] = "CONDITION"; + } + + // Add the result/value + j["result"] = getObservationValue(observation); + + // Add sequence number + j["sequence"] = observation->getSequence(); + + auto result = j.dump(); + LOG(trace) << "Formatted observation JSON: " << result; + return result; + } + catch (const std::exception& e) + { + LOG(error) << "Exception formatting observation: " << e.what(); + return "{}"; + } + } + + std::string MqttEntitySink::formatConditionJson(const observation::ConditionPtr& condition) + { + json j; + + auto dataItem = condition->getDataItem(); + if (!dataItem) + { + return "{}"; + } + + j["dataItemId"] = dataItem->getId(); + + const auto& name = dataItem->getName(); + if (name && !name->empty()) + { + j["name"] = *name; + } + + j["type"] = dataItem->getType(); + + auto subType = dataItem->maybeGet("subType"); + if (subType && !subType->empty()) + { + j["subType"] = *subType; + } + + j["timestamp"] = formatTimestamp(condition->getTimestamp()); + j["category"] = "CONDITION"; + + // Add condition-specific fields + switch (condition->getLevel()) + { + case Condition::NORMAL: + j["level"] = "NORMAL"; + break; + case Condition::WARNING: + j["level"] = "WARNING"; + break; + case Condition::FAULT: + j["level"] = "FAULT"; + break; + case Condition::UNAVAILABLE: + j["level"] = "UNAVAILABLE"; + break; + } + + // Add native code if present + if (condition->hasProperty("nativeCode")) + { + j["nativeCode"] = condition->get("nativeCode"); + } + + // Add condition ID if present + if (!condition->getCode().empty()) + { + j["conditionId"] = condition->getCode(); + } + + // Add message/value if present + if (condition->hasValue()) + { + j["message"] = getObservationValue(condition); + } + + // Add sequence number + j["sequence"] = condition->getSequence(); + + return j.dump(); + } + + std::string MqttEntitySink::getObservationTopic( + const observation::ObservationPtr& observation) + { + auto dataItem = observation->getDataItem(); + if (!dataItem) + { + return ""; + } + + auto device = dataItem->getComponent()->getDevice(); + if (!device) + { + return ""; + } + + std::string topic = m_observationTopicPrefix; + boost::replace_all(topic, "[device]", *device->getUuid()); + + // Append data item ID for flat structure + topic += "/" + dataItem->getId(); + + return topic; + } + + bool MqttEntitySink::publish(observation::ObservationPtr& observation) + { + auto dataItem = observation->getDataItem(); + if (!dataItem) + { + LOG(warning) << "MqttEntitySink::publish: Observation has no data item"; + return false; + } + + if (!m_client || !m_client->isConnected()) + { + std::lock_guard lock(m_queueMutex); + if (m_queuedObservations.size() >= MAX_QUEUE_SIZE) + { + LOG(warning) << "MqttEntitySink::publish: Observation queue full (" << MAX_QUEUE_SIZE + << "), dropping oldest observation for " + << m_queuedObservations.front()->getDataItem()->getId(); + m_queuedObservations.erase(m_queuedObservations.begin()); + } + LOG(debug) << "MqttEntitySink::publish: Client not connected, queuing observation for " + << dataItem->getId(); + m_queuedObservations.push_back(observation); + return false; + } + + std::string topic = getObservationTopic(observation); + if (topic.empty()) + { + LOG(warning) << "MqttEntitySink::publish: Empty topic for " << dataItem->getId(); + return false; + } + + // Get QoS setting + auto qos = static_cast( + GetOption(m_options, configuration::MqttQOS).value_or(1)); + bool retain = GetOption(m_options, configuration::MqttRetain).value_or(false); + + try + { + auto condition = dynamic_pointer_cast(observation); + if (condition) + { + observation::ConditionList condList; + condition->getFirst()->getConditionList(condList); + + for (auto& cond : condList) + { + std::string payload = formatConditionJson(cond); + LOG(debug) << "Publishing condition to: " << topic + << ", payload size: " << payload.size(); + m_client->publish(topic, payload, retain, qos); + } + } + else + { + std::string payload = formatObservationJson(observation); + LOG(debug) << "Publishing observation to: " << topic << ", size: " << payload.size(); + m_client->publish(topic, payload, retain, qos); + } + + return true; + } + catch (const std::exception& e) + { + LOG(error) << "Exception publishing observation: " << e.what(); + return false; + } + } + + bool MqttEntitySink::publish(device_model::DevicePtr device) + { + if (!m_client || !m_client->isConnected()) + { + return false; + } + + // For device, we could publish the device XML or JSON + // For now, just return true as device publishing is optional + return true; + } + + bool MqttEntitySink::publish(asset::AssetPtr asset) + { + if (!m_client || !m_client->isConnected()) + { + return false; + } + + // Asset publishing can be added here if needed + return true; + } + + void MqttEntitySink::registerFactory(SinkFactory& factory) + { + factory.registerFactory( + "MqttEntitySink", + [](const std::string& name, boost::asio::io_context& io, SinkContractPtr&& contract, + const ConfigOptions& options, const boost::property_tree::ptree& block) -> SinkPtr { + auto sink = std::make_shared(io, std::move(contract), options, block); + return sink; + }); + } + + } // namespace mqtt_entity_sink + } // namespace sink +} // namespace mtconnect diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp new file mode 100644 index 000000000..a0ae72f15 --- /dev/null +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include "boost/asio/io_context.hpp" +#include + +#include + +#include "mtconnect/buffer/checkpoint.hpp" +#include "mtconnect/config.hpp" +#include "mtconnect/configuration/agent_config.hpp" +#include "mtconnect/mqtt/mqtt_client.hpp" +#include "mtconnect/observation/observation.hpp" +#include "mtconnect/sink/sink.hpp" +#include "mtconnect/utilities.hpp" + +using namespace std; +using namespace mtconnect; +using namespace mtconnect::mqtt_client; +using json = nlohmann::json; + +namespace mtconnect { + namespace sink { + namespace mqtt_entity_sink { + + /// @brief MTConnect Entity MQTT Sink - publishes observations per data item + class AGENT_LIB_API MqttEntitySink : public sink::Sink + { + public: + /// @brief Create an MQTT Entity Sink + /// @param context the boost asio io_context + /// @param contract the Sink Contract from the agent + /// @param options configuration options + /// @param config additional configuration options + MqttEntitySink(boost::asio::io_context& context, sink::SinkContractPtr&& contract, + const ConfigOptions& options, const boost::property_tree::ptree& config); + + ~MqttEntitySink() = default; + + // Sink Methods + /// @brief Start the MQTT Entity service + void start() override; + + /// @brief Shutdown the MQTT Entity service + void stop() override; + + /// @brief Receive an observation and publish it + /// @param observation shared pointer to the observation + /// @return `true` if the publishing was successful + bool publish(observation::ObservationPtr& observation) override; + + /// @brief Receive an asset + /// @param asset shared point to the asset + /// @return `true` if successful + bool publish(asset::AssetPtr asset) override; + + /// @brief Receive a device + /// @param device shared pointer to the device + /// @return `true` if successful + bool publish(device_model::DevicePtr device) override; + + /// @brief Register the Sink factory to create this sink + /// @param factory + static void registerFactory(SinkFactory& factory); + + /// @brief Gets the MQTT Client + /// @return MqttClient + std::shared_ptr getClient() { return m_client; } + + /// @brief Check if MQTT Client is Connected + /// @return `true` when the client is connected + bool isConnected() { return m_client && m_client->isConnected(); } + + protected: + /// @brief Format observation as JSON matching MTConnect.NET format + /// @param observation the observation to format + /// @return JSON string + std::string formatObservationJson(const observation::ObservationPtr& observation); + + /// @brief Format condition observation as JSON + /// @param condition the condition observation + /// @return JSON string + std::string formatConditionJson(const observation::ConditionPtr& condition); + + /// @brief Get topic for observation using flat structure + /// @param observation the observation + /// @return formatted topic string + std::string getObservationTopic(const observation::ObservationPtr& observation); + + /// @brief Get value from observation as string + /// @param observation the observation + /// @return value as string + std::string getObservationValue(const observation::ObservationPtr& observation); + + /// @brief Publish initial device and current observations + void publishInitialContent(); + + /// @brief Convert timestamp to ISO 8601 format + /// @param timestamp the timestamp + /// @return ISO 8601 string + std::string formatTimestamp(const Timestamp& timestamp); + + protected: + static constexpr size_t MAX_QUEUE_SIZE = 10000; // Maximum queued observations + + std::string m_observationTopicPrefix; //! Observation topic prefix + std::string m_deviceTopicPrefix; //! Device topic prefix + std::string m_assetTopicPrefix; //! Asset topic prefix + std::string m_lastWillTopic; //! Topic to publish last will + + boost::asio::io_context& m_context; + boost::asio::io_context::strand m_strand; + + ConfigOptions m_options; + + std::shared_ptr m_client; + std::vector m_queuedObservations; + std::mutex m_queueMutex; + }; + } // namespace mqtt_entity_sink + } // namespace sink +} // namespace mtconnect From f1629206e71c8db8c897e6c6fcce51be72c07659 Mon Sep 17 00:00:00 2001 From: virajdere Date: Fri, 24 Oct 2025 21:53:44 +0000 Subject: [PATCH 23/84] updated qos config and added tests with feature documentation --- README.md | 4 + schemas/cfg.schema.json | 67 ++ src/mtconnect/mqtt/mqtt_server_impl.hpp | 11 +- src/mtconnect/sink/mqtt_entity_sink/README.md | 243 ++++++ .../mqtt_entity_sink/mqtt_entity_sink.cpp | 46 +- .../mqtt_entity_sink/mqtt_entity_sink.hpp | 17 + test_package/CMakeLists.txt | 1 + test_package/agent_test_helper.hpp | 22 + test_package/mqtt_entity_sink_test.cpp | 706 ++++++++++++++++++ 9 files changed, 1106 insertions(+), 11 deletions(-) create mode 100644 src/mtconnect/sink/mqtt_entity_sink/README.md create mode 100644 test_package/mqtt_entity_sink_test.cpp diff --git a/README.md b/README.md index da2f859ce..e1647b101 100755 --- a/README.md +++ b/README.md @@ -904,6 +904,10 @@ Sinks { *Default*: All data items +## MQTT Entity Sink Documentation + +For detailed configuration, usage, and message format for the MQTT Entity Sink, see: [docs: MTConnect MQTT Entity Sink](src/mtconnect/sink/mqtt_entity_sink/README.md) + ### Adapter Configuration Items ### * `Adapters` - Contains a list of device blocks. If there are no Adapters diff --git a/schemas/cfg.schema.json b/schemas/cfg.schema.json index 182341b3a..b81c9c9bf 100644 --- a/schemas/cfg.schema.json +++ b/schemas/cfg.schema.json @@ -314,6 +314,73 @@ "enum":["at_least_once", "at_most_once", "exactly_once"] } } + }, + "MqttEntitySink": { + "type": "object", + "properties": { + "MqttHost": { + "type": "string", + "description": "IP Address or name of the MQTT Broker", + "default": "127.0.0.1" + }, + "MqttPort": { + "type": "integer", + "description": "Port number of MQTT Broker", + "default": 1883 + }, + "MqttTls": { + "type": "boolean", + "description": "TLS Certificate for secure connection to the MQTT Broker", + "default": false + }, + "MqttUserName": { + "type": "string", + "description": "Username for MQTT authentication" + }, + "MqttPassword": { + "type": "string", + "description": "Password for MQTT authentication" + }, + "MqttClientId": { + "type": "string", + "description": "MQTT client identifier" + }, + "MqttQOS": { + "type": "string", + "description": "The quality of service level for the MQTT connection. Options are: at_least_once, at_most_once, and exactly_once", + "default": "at_least_once", + "enum": [ + "at_least_once", + "at_most_once", + "exactly_once" + ] + }, + "MqttRetain": { + "type": "boolean", + "description": "Retain the last message sent to the broker", + "default": false + }, + "ObservationTopicPrefix": { + "type": "string", + "description": "Prefix for the Observations topic", + "default": "MTConnect/Devices/[device]/Observations" + }, + "DeviceTopicPrefix": { + "type": "string", + "description": "Prefix for the Device topic", + "default": "MTConnect/Probe/[device]" + }, + "AssetTopicPrefix": { + "type": "string", + "description": "Prefix for the Asset topic", + "default": "MTConnect/Asset/[device]" + }, + "MqttLastWillTopic": { + "type": "string", + "description": "The topic used for the last will and testament for an agent", + "default": "MTConnect/Probe/[device]/Availability" + } + } } } }, diff --git a/src/mtconnect/mqtt/mqtt_server_impl.hpp b/src/mtconnect/mqtt/mqtt_server_impl.hpp index ce8a9f621..b962d14ab 100644 --- a/src/mtconnect/mqtt/mqtt_server_impl.hpp +++ b/src/mtconnect/mqtt/mqtt_server_impl.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "mqtt_server.hpp" #include "mtconnect/configuration/config_options.hpp" @@ -217,12 +218,12 @@ namespace mtconnect { LOG(debug) << "Server topic_name: " << topic_name; LOG(debug) << "Server contents: " << contents; - auto const &idx = m_subs.get(); - auto r = idx.equal_range(topic_name); - for (; r.first != r.second; ++r.first) + for (const auto& sub : m_subs) { - r.first->con->publish(topic_name, contents, - std::min(r.first->qos_value, pubopts.get_qos())); + if (mqtt::broker::compare_topic_filter(sub.topic, topic_name)) + { + sub.con->publish(topic_name, contents, std::min(sub.qos_value, pubopts.get_qos())); + } } return true; diff --git a/src/mtconnect/sink/mqtt_entity_sink/README.md b/src/mtconnect/sink/mqtt_entity_sink/README.md new file mode 100644 index 000000000..c01d2eea7 --- /dev/null +++ b/src/mtconnect/sink/mqtt_entity_sink/README.md @@ -0,0 +1,243 @@ +# MTConnect MQTT Entity Sink + +## Overview + +The MQTT Entity Sink publishes MTConnect observations to an external MQTT broker in a flat, per-data-item topic structure compatible with MTConnect.NET's MQTT Relay Entity format. + +## Features + +- **Real-time streaming** of observations +- **Flat topic structure**: `MTConnect/Devices/[device-uuid]/Observations/[dataItemId]` +- **JSON format** with full observation metadata +- **ISO 8601 timestamps** +- **Supports SAMPLE, EVENT, and CONDITION observations** +- **Last Will Testament** for availability +- **TLS/SSL support** for secure communication +- **Configurable QoS levels** for MQTT message delivery +- **Message retention** support for new subscribers + +## Quick Start + +### 1. Configuration + +Add to `agent.cfg`: + +```properties +Sinks { + MqttEntitySink { + MqttHost = localhost + MqttPort = 1883 + MqttTls = false + + # QoS levels (string values): + # at_most_once = Fire and forget + # at_least_once = Acknowledged delivery (default) + # exactly_once = Guaranteed delivery + MqttQOS = at_least_once + + # Retain last message on broker for new subscribers + MqttRetain = false + } +} +``` + +## Configuration Options + +### Connection Settings + +| Parameter | Type | Default | Description | +|-------------------|---------|-------------|-----------------------------------------------| +| **MqttHost** | string | `127.0.0.1` | MQTT broker hostname or IP address | +| **MqttPort** | integer | `1883` | MQTT broker port (1883 for TCP, 8883 for TLS) | +| **MqttTls** | boolean | `false` | Enable TLS encryption | +| **MqttClientId** | string | auto-gen | MQTT client identifier (unique per connection) | + +### Authentication + +| Parameter | Type | Default | Description | +|-------------------|---------|---------|-----------------------------------------------| +| **MqttUserName** | string | - | Username for MQTT authentication (optional) | +| **MqttPassword** | string | - | Password for MQTT authentication (optional) | + +### TLS/SSL Settings + +| Parameter | Type | Default | Description | +|-------------------|---------|---------|-----------------------------------------------| +| **MqttCaCert** | string | - | Path to CA certificate for TLS verification | +| **MqttCert** | string | - | Path to client certificate for mutual TLS | +| **MqttPrivateKey**| string | - | Path to client private key | + +### Topic Configuration + +| Parameter | Type | Default | Description | +|--------------------------|---------|-------------------------------------------|----------------------------------------------| +| **ObservationTopicPrefix**| string | `MTConnect/Devices/[device]/Observations` | Topic prefix for observations. `[device]` is replaced with device UUID | +| **DeviceTopicPrefix** | string | `MTConnect/Probe/[device]` | Topic prefix for device models | +| **AssetTopicPrefix** | string | `MTConnect/Asset/[device]` | Topic prefix for assets | +| **MqttLastWillTopic** | string | `MTConnect/Probe/[device]/Availability` | Last Will Testament topic for availability | + +### MQTT Protocol Settings + +| Parameter | Type | Default | Description | +|---------------|---------|-----------------|---------------------------------------------| +| **MqttQOS** | string | `at_least_once` | Quality of Service level for MQTT messages | +| **MqttRetain**| boolean | `false` | Retain messages on the broker for new subscribers | + +#### MqttQOS Values + +The Quality of Service (QoS) level determines how messages are delivered: + +- **at_most_once** (Fire and forget) + - Fastest delivery + - No acknowledgment required + - Message may be lost if network fails + - **Use case**: High-frequency data where occasional loss is acceptable + +- **at_least_once** (Default) + - Guaranteed delivery with acknowledgment + - Messages may be duplicated + - Balance of reliability and performance + - **Use case**: General-purpose MTConnect data streaming + +- **exactly_once** + - Guaranteed exactly-once delivery + - Highest reliability, slowest performance + - Four-way handshake protocol + - **Use case**: Critical alarms, conditions, or important events + +```properties +# Example: High-reliability configuration for critical data +MqttQOS = exactly_once +``` + +#### MqttRetain Flag + +The retain flag determines if the broker stores the last message for each topic: + +- **false** (Default) + - Messages are not retained + - New subscribers only receive future messages + - Lower broker storage requirements + - **Use case**: High-frequency streaming data + +- **true** + - Broker stores the last message per topic + - New subscribers immediately receive last known value + - Useful for status and current values + - **Use case**: Availability, current state, alarms + +```properties +# Example: Retain messages for immediate status updates +MqttRetain = true +``` + +**⚠️ Important Notes:** +- Retained messages persist on the broker until explicitly cleared +- Use retention sparingly with high-frequency data to avoid broker storage issues +- Retained availability messages help dashboards show immediate device status + +## Complete Configuration Example + +```properties +Sinks { + MqttEntitySink { + MqttHost = localhost + MqttPort = 1883 + MqttTls = false + MqttQOS = at_least_once + MqttRetain = false + ObservationTopicPrefix = MTConnect/Devices/[device]/Observations + DeviceTopicPrefix = MTConnect/Probe/[device] + AssetTopicPrefix = MTConnect/Asset/[device] + MqttLastWillTopic = MTConnect/Probe/[device]/Availability + } +} +``` + +## Message Format + +### SAMPLE Observation +```json +{ + "dataItemId": "Xact", + "name": "X_actual", + "type": "POSITION", + "subType": "ACTUAL", + "category": "SAMPLE", + "timestamp": "2025-10-20T15:30:45.123456Z", + "result": "150.500000", + "sequence": 1234 +} +``` + +### EVENT Observation +```json +{ + "dataItemId": "mode", + "name": "ControllerMode", + "type": "CONTROLLER_MODE", + "category": "EVENT", + "timestamp": "2025-10-20T15:30:46.000000Z", + "result": "AUTOMATIC", + "sequence": 1235 +} +``` + +### CONDITION Observation +```json +{ + "dataItemId": "system", + "name": "SystemCondition", + "type": "SYSTEM", + "category": "CONDITION", + "level": "FAULT", + "nativeCode": "E001", + "conditionId": "E001", + "message": "Hydraulic pressure low", + "timestamp": "2025-10-20T15:30:47.000000Z", + "sequence": 1236 +} +``` + +## Topic Structure + +``` +MTConnect/ +├── Devices/ +│ └── {device-uuid}/ +│ └── Observations/ +│ ├── {dataItemId1} → Observation JSON +│ ├── {dataItemId2} → Observation JSON +│ └── ... +└── Probe/ + └── {device-uuid}/ + └── Availability → AVAILABLE/UNAVAILABLE (LWT) +``` + +**Example Topics:** +``` +MTConnect/Devices/OKUMA.123456/Observations/Xact +MTConnect/Devices/OKUMA.123456/Observations/avail +MTConnect/Probe/OKUMA.123456/Availability +``` + +## Performance Considerations + +### QoS Impact + +- **at_most_once**: ~2x faster than at_least_once, best for high-frequency sampling data +- **at_least_once**: Balanced performance, suitable for most use cases +- **exactly_once**: ~2x slower than at_least_once, use only for critical data + +### Retain Flag Impact + +- **Without Retain**: Minimal broker storage, suitable for streaming +- **With Retain**: Increases broker storage per topic, use selectively for: + - Availability status + - Current operating mode + - Alarm states + - Device health indicators + +**💡 Recommendation**: Use `MqttRetain = true` only for low-frequency, stateful data. + +--- diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp index 488e50ba8..8c288919a 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp @@ -1,3 +1,20 @@ +// +// Copyright Copyright 2009-2023, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + #include "mqtt_entity_sink.hpp" #include @@ -60,6 +77,26 @@ namespace mtconnect { m_assetTopicPrefix = get(m_options[configuration::AssetTopicPrefix]); } + static MqttClient::QOS parseQos(const ConfigOptions& options) + { + if (auto qosInt = GetOption(options, configuration::MqttQOS)) + { + return *qosInt == 0 ? MqttClient::QOS::at_most_once + : *qosInt == 2 ? MqttClient::QOS::exactly_once + : MqttClient::QOS::at_least_once; + } + + if (auto qosStr = GetOption(options, configuration::MqttQOS)) + { + if (*qosStr == "at_most_once" || *qosStr == "0") + return MqttClient::QOS::at_most_once; + if (*qosStr == "exactly_once" || *qosStr == "2") + return MqttClient::QOS::exactly_once; + } + + return MqttClient::QOS::at_least_once; + } + void MqttEntitySink::start() { if (!m_client) @@ -76,8 +113,7 @@ namespace mtconnect { boost::replace_all(lwtTopic, "[device]", *agentDevice->getUuid()); // Get QoS and Retain from options - auto qos = static_cast( - GetOption(m_options, configuration::MqttQOS).value_or(1)); + auto qos = parseQos(m_options); bool retain = GetOption(m_options, configuration::MqttRetain).value_or(false); LOG(debug) << "Publishing availability to: " << lwtTopic; @@ -118,8 +154,7 @@ namespace mtconnect { // Publish UNAVAILABLE before disconnecting if (m_client->isConnected()) { - auto qos = static_cast( - GetOption(m_options, configuration::MqttQOS).value_or(1)); + auto qos = parseQos(m_options); m_client->publish(m_lastWillTopic, "UNAVAILABLE", true, qos); } m_client->stop(); @@ -450,8 +485,7 @@ namespace mtconnect { } // Get QoS setting - auto qos = static_cast( - GetOption(m_options, configuration::MqttQOS).value_or(1)); + auto qos = parseQos(m_options); bool retain = GetOption(m_options, configuration::MqttRetain).value_or(false); try diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp index a0ae72f15..34e248ad6 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp @@ -1,3 +1,20 @@ +// +// Copyright Copyright 2009-2023, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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 "boost/asio/io_context.hpp" diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 2cd035cc7..2dc65edb9 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -297,6 +297,7 @@ add_agent_test(table TRUE observation) add_agent_test(checkpoint FALSE buffer) add_agent_test(circular_buffer FALSE buffer) +add_agent_test(mqtt_entity_sink FALSE sink/mqtt_entity_sink TRUE) if (WITH_RUBY) diff --git a/test_package/agent_test_helper.hpp b/test_package/agent_test_helper.hpp index 45ff79c00..fcbeb0402 100644 --- a/test_package/agent_test_helper.hpp +++ b/test_package/agent_test_helper.hpp @@ -29,6 +29,7 @@ #include "mtconnect/configuration/agent_config.hpp" #include "mtconnect/configuration/config_options.hpp" #include "mtconnect/pipeline/pipeline.hpp" +#include "mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp" #include "mtconnect/sink/mqtt_sink/mqtt_service.hpp" #include "mtconnect/sink/rest_sink/response.hpp" #include "mtconnect/sink/rest_sink/rest_service.hpp" @@ -169,6 +170,13 @@ class AgentTestHelper return rest; } + std::shared_ptr getMqttEntitySink() + { + using namespace mtconnect::sink::mqtt_entity_sink; + auto sink = m_agent->findSink("MqttEntitySink"); + return std::dynamic_pointer_cast(sink); + } + std::shared_ptr getMqttService() { using namespace mtconnect; @@ -189,6 +197,7 @@ class AgentTestHelper sink::rest_sink::RestService::registerFactory(m_sinkFactory); sink::mqtt_sink::MqttService::registerFactory(m_sinkFactory); + sink::mqtt_entity_sink::MqttEntitySink::registerFactory(m_sinkFactory); source::adapter::shdr::ShdrAdapter::registerFactory(m_sourceFactory); ConfigOptions options = ops; @@ -230,6 +239,17 @@ class AgentTestHelper m_agent->addSink(m_mqttService); } + if (HasOption(options, "MqttEntitySink")) + { + auto mqttEntityContract = m_agent->makeSinkContract(); + mqttEntityContract->m_pipelineContext = m_context; + auto mqttEntitySink = m_sinkFactory.make("MqttEntitySink", "MqttEntitySink", m_ioContext, + std::move(mqttEntityContract), options, ptree {}); + m_mqttEntitySink = + std::dynamic_pointer_cast(mqttEntitySink); + m_agent->addSink(m_mqttEntitySink); + } + m_agent->initialize(m_context); if (observe) @@ -303,6 +323,8 @@ class AgentTestHelper std::shared_ptr m_context; std::shared_ptr m_adapter; std::shared_ptr m_mqttService; + std::shared_ptr + m_mqttEntitySink; std::shared_ptr m_restService; std::shared_ptr m_loopback; diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp new file mode 100644 index 000000000..7ea003c7a --- /dev/null +++ b/test_package/mqtt_entity_sink_test.cpp @@ -0,0 +1,706 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology ("AMT") +// All rights reserved. +// +// 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. +// + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include + +#include + +#include "agent_test_helper.hpp" +#include "json_helper.hpp" +#include "mtconnect/buffer/checkpoint.hpp" +#include "mtconnect/device_model/data_item/data_item.hpp" +#include "mtconnect/entity/entity.hpp" +#include "mtconnect/entity/json_parser.hpp" +#include "mtconnect/mqtt/mqtt_client_impl.hpp" +#include "mtconnect/mqtt/mqtt_server_impl.hpp" +#include "mtconnect/printer/json_printer.hpp" +#include "mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp" +#include "test_utilities.hpp" + +using namespace std; +using namespace mtconnect; +using namespace mtconnect::device_model::data_item; +using namespace mtconnect::sink::mqtt_entity_sink; +using namespace mtconnect::asset; +using namespace mtconnect::configuration; + +// main +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +using json = nlohmann::json; + +class MqttEntitySinkTest : public testing::Test +{ +protected: + std::unique_ptr m_agentTestHelper; + std::shared_ptr m_server; + std::shared_ptr m_client; + std::unique_ptr m_jsonPrinter; + uint16_t m_port {0}; + + void SetUp() override { + m_agentTestHelper = std::make_unique(); + m_jsonPrinter = std::make_unique(2, true); + } + + void TearDown() override { + if (m_client) { + m_client->stop(); + m_agentTestHelper->m_ioContext.run_for(500ms); + m_client.reset(); + } + if (m_server) { + m_server->stop(); + m_agentTestHelper->m_ioContext.run_for(500ms); + m_server.reset(); + } + m_agentTestHelper.reset(); + m_jsonPrinter.reset(); + } + + void createAgent(std::string testFile = {}, ConfigOptions options = {}) { + if (testFile == "") + testFile = "/samples/test_config.xml"; + ConfigOptions opts(options); + MergeOptions( + opts, + { + {"MqttEntitySink", true}, + {configuration::MqttPort, m_port}, + {MqttCurrentInterval, 200ms}, + {MqttSampleInterval, 100ms}, + {configuration::MqttHost, "127.0.0.1"s}, + {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, + {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, + {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, + {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, + }); + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 25, false, true, opts); + addAdapter(); + m_agentTestHelper->getAgent()->start(); + } + + void createServer(const ConfigOptions& options) { + using namespace mtconnect::configuration; + ConfigOptions opts(options); + MergeOptions(opts, {{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); + m_server = std::make_shared(m_agentTestHelper->m_ioContext, opts); + } + + template + bool waitFor(const chrono::duration& time, function pred) { + boost::asio::steady_timer timer(m_agentTestHelper->m_ioContext); + timer.expires_after(time); + bool timeout = false; + timer.async_wait([&timeout](boost::system::error_code ec) { + if (!ec) timeout = true; + }); + while (!timeout && !pred()) { + m_agentTestHelper->m_ioContext.run_for(100ms); + } + timer.cancel(); + return pred(); + } + + void startServer() { + if (m_server) { + bool start = m_server->start(); + if (start) { + m_port = m_server->getPort(); + m_agentTestHelper->m_ioContext.run_for(500ms); + } + } + } + + void createClient(const ConfigOptions& options, unique_ptr&& handler) { + ConfigOptions opts(options); + MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, + {MqttPort, m_port}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); + m_client = make_shared(m_agentTestHelper->m_ioContext, + opts, std::move(handler)); + } + + bool startClient() { + bool started = m_client && m_client->start(); + if (started) { + return waitFor(1s, [this]() { return m_client->isConnected(); }); + } + return started; + } + + void addAdapter(ConfigOptions options = ConfigOptions {}) { + m_agentTestHelper->addAdapter(options, "localhost", 0, + m_agentTestHelper->m_agent->getDefaultDevice()->getName()); + } +}; + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) +{ + bool gotMessage = false; + std::string receivedTopic; + + createServer({}); + startServer(); + + auto client = mqtt::make_async_client(m_agentTestHelper->m_ioContext.get(), "localhost", m_port); + + client->set_client_id("test_client"); + client->set_clean_session(true); + client->set_keep_alive_sec(30); + + bool subscribed = false; + client->set_connack_handler( + [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { + if (connack_return_code == mqtt::connect_return_code::accepted) + { + auto pid = client->acquire_unique_packet_id(); + client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, + [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); + } + return true; + }); + + client->set_suback_handler( + [&subscribed](std::uint16_t packet_id, std::vector results) { + subscribed = true; + return true; + }); + + client->set_publish_handler([&gotMessage, &receivedTopic](mqtt::optional packet_id, + mqtt::publish_options pubopts, + mqtt::buffer topic_name, + mqtt::buffer contents) { + receivedTopic = topic_name; + std::cout << "Received topic: " << topic_name << " payload: " << contents << std::endl; + if (topic_name.find("MTConnect/Devices/") == 0 && + topic_name.find("/Observations/") != std::string::npos) + { + gotMessage = true; + } + return true; + }); + + client->async_connect([](mqtt::error_code ec) { ASSERT_FALSE(ec) << "Cannot connect"; }); + + // Wait for subscription to complete + ASSERT_TRUE(waitFor(10s, [&subscribed]() { return subscribed; })) + << "Subscription never completed"; + + // Create the agent and wait for its MQTT sink to connect. + createAgent(); + auto sink = m_agentTestHelper->getMqttEntitySink(); + ASSERT_TRUE(sink != nullptr); + ASSERT_TRUE(waitFor(10s, [&sink]() { return sink->isConnected(); })) + << "MqttEntitySink failed to connect to broker"; + + gotMessage = false; + receivedTopic.clear(); + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); + ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })) + << "Timeout waiting for adapter data. Last topic: " << receivedTopic; + + EXPECT_TRUE(receivedTopic.find("MTConnect/Devices/") == 0) + << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; + EXPECT_TRUE(receivedTopic.find("/Observations/") != std::string::npos) + << "Topic doesn't contain /Observations/: " << receivedTopic; + EXPECT_EQ("MTConnect/Devices/000/Observations/p3", receivedTopic) + << "Topic does not match expected format: " << receivedTopic; + + client->async_disconnect(); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotMessage = false; + json receivedJson; + + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + try + { + receivedJson = json::parse(payload); + // Only set gotMessage if result matches expected value + if (receivedJson.contains("result") && receivedJson["result"] == "204") + { + gotMessage = true; + } + } + catch (const std::exception& e) + { + LOG(error) << "Failed to parse JSON: " << e.what(); + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + // Ensure subscription is active before sending data + m_agentTestHelper->m_ioContext.run_for(200ms); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + // Wait a bit to ensure sink and client are ready + m_agentTestHelper->m_ioContext.run_for(200ms); + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); + ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); + + // Verify Entity JSON format + EXPECT_TRUE(receivedJson.contains("dataItemId")); + EXPECT_TRUE(receivedJson.contains("timestamp")); + EXPECT_TRUE(receivedJson.contains("result")); + EXPECT_TRUE(receivedJson.contains("sequence")); + EXPECT_TRUE(receivedJson.contains("type")); + EXPECT_TRUE(receivedJson.contains("category")); + + EXPECT_EQ("204", receivedJson["result"].get()); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotMessage = false; + json receivedJson; + + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + receivedJson = json::parse(payload); + if (receivedJson.contains("name")) + { + gotMessage = true; + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); + ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); + + // Verify optional fields + if (receivedJson.contains("name")) + { + EXPECT_TRUE(receivedJson["name"].is_string()); + } + if (receivedJson.contains("subType")) + { + EXPECT_TRUE(receivedJson["subType"].is_string()); + } +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotSample = false; + json receivedJson; + + handler->m_receive = [&gotSample, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + try + { + receivedJson = json::parse(payload); + if (receivedJson.contains("dataItemId") && receivedJson["dataItemId"] == "z2" && + receivedJson.contains("category") && receivedJson["category"] == "SAMPLE" && + receivedJson.contains("result") && receivedJson["result"] == "204.000000") + { + gotSample = true; + } + } + catch (const std::exception& e) + { + LOG(error) << "Failed to parse JSON: " << e.what(); + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + m_agentTestHelper->m_ioContext.run_for(200ms); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + m_agentTestHelper->m_ioContext.run_for(200ms); + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|z2|204"); + ASSERT_TRUE(waitFor(10s, [&gotSample]() { return gotSample; })); + + EXPECT_EQ("SAMPLE", receivedJson["category"].get()); + EXPECT_EQ("204.000000", receivedJson["result"].get()); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotEvent = false; + json receivedJson; + + handler->m_receive = [&gotEvent, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + try { + receivedJson = json::parse(payload); + if (receivedJson.contains("category") && receivedJson["category"] == "EVENT" && + receivedJson.contains("dataItemId") && receivedJson["dataItemId"] == "p4" && + receivedJson.contains("result") && receivedJson["result"] == "READY") + { + gotEvent = true; + } + } catch (const std::exception& e) { + LOG(error) << "Failed to parse JSON: " << e.what(); + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + m_agentTestHelper->m_ioContext.run_for(200ms); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + m_agentTestHelper->m_ioContext.run_for(200ms); + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|p4|READY"); + ASSERT_TRUE(waitFor(10s, [&gotEvent]() { return gotEvent; })); + + EXPECT_EQ("EVENT", receivedJson["category"].get()); + EXPECT_EQ("READY", receivedJson["result"].get()); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotCondition = false; + json receivedJson; + + handler->m_receive = [&gotCondition, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + try { + receivedJson = json::parse(payload); + if (receivedJson.contains("category") && receivedJson["category"] == "CONDITION" && + receivedJson.contains("dataItemId") && receivedJson["dataItemId"] == "zlc" && + receivedJson.contains("level") && receivedJson["level"] == "FAULT") + { + gotCondition = true; + } + } catch (const std::exception& e) { + LOG(error) << "Failed to parse JSON: " << e.what(); + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + m_agentTestHelper->m_ioContext.run_for(200ms); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + m_agentTestHelper->m_ioContext.run_for(200ms); + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); + ASSERT_TRUE(waitFor(10s, [&gotCondition]() { return gotCondition; })); + + EXPECT_EQ("CONDITION", receivedJson["category"].get()); + EXPECT_EQ("FAULT", receivedJson["level"].get()); + if (receivedJson.contains("nativeCode")) + { + EXPECT_EQ("1234", receivedJson["nativeCode"].get()); + } +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotAvailable = false; + std::string availabilityValue; + + handler->m_receive = [&gotAvailable, &availabilityValue](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + if (topic.find("Availability") != std::string::npos) + { + availabilityValue = payload; + gotAvailable = true; + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Probe/#"); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + ASSERT_TRUE(waitFor(5s, [&gotAvailable]() { return gotAvailable; })); + EXPECT_EQ("AVAILABLE", availabilityValue); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + int messageCount = 0; + + handler->m_receive = [&messageCount](std::shared_ptr client, const std::string& topic, + const std::string& payload) { + if (topic.find("/Observations/") != std::string::npos) + { + messageCount++; + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + ASSERT_TRUE(waitFor(10s, [&messageCount]() { return messageCount > 0; })); + EXPECT_GT(messageCount, 0); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) +{ + ConfigOptions options; + + createServer({}); + startServer(); + + auto handler = make_unique(); + bool gotUnavailable = false; + json receivedJson; + + handler->m_receive = [&gotUnavailable, &receivedJson](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { + receivedJson = json::parse(payload); + if (receivedJson["result"] == "UNAVAILABLE") + { + gotUnavailable = true; + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + + createAgent(); + + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); + + // Initial observations should include UNAVAILABLE values + ASSERT_TRUE(waitFor(10s, [&gotUnavailable]() { return gotUnavailable; })); + EXPECT_EQ("UNAVAILABLE", receivedJson["result"].get()); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_authentication) +{ + ConfigOptions options; + options["MqttUserName"] = "mtconnect"; + options["MqttPassword"] = "password123"; + options["MqttClientId"] = "auth-client"; + + createServer({}); + startServer(); + + bool connected = false; + auto handler = std::make_unique(); + handler->m_connected = [&connected](std::shared_ptr) { + connected = true; + }; + + createClient(options, std::move(handler)); + startClient(); + + auto start = std::chrono::steady_clock::now(); + while (!connected && std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + ASSERT_TRUE(connected) << "MQTT client did not connect with authentication"; +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_qos_levels) +{ + ConfigOptions options; + options["MqttQOS"] = "exactly_once"; + options["MqttClientId"] = "qos-client"; + + createServer({}); + startServer(); + + auto handler = std::make_unique(); + bool received = false; + handler->m_receive = [&received](std::shared_ptr client, const std::string& topic, const std::string& payload) { + received = true; + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + + createAgent(); + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = std::dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); + + ASSERT_TRUE(waitFor(5s, [&received]() { return received; })); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) +{ + ConfigOptions options; + options["MqttRetain"] = "true"; + options["MqttClientId"] = "retain-client"; + + createServer({}); + startServer(); + + auto handler = std::make_unique(); + bool retainedReceived = false; + std::string retainedPayload; + handler->m_receive = [&retainedReceived, &retainedPayload](std::shared_ptr client, const std::string& topic, const std::string& payload) { + retainedReceived = true; + retainedPayload = payload; + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Devices/#"); + + createAgent(); + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = std::dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); + + ASSERT_TRUE(waitFor(5s, [&retainedReceived]() { return retainedReceived; })); + ASSERT_FALSE(retainedPayload.empty()); +} + +TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) +{ + ConfigOptions options; + options["MqttLastWillTopic"] = "MTConnect/Probe/J55-411045-cpp/Availability"; + options["MqttClientId"] = "lastwill-client"; + + createServer({}); + startServer(); + + auto handler = std::make_unique(); + bool lastWillReceived = false; + handler->m_receive = [&lastWillReceived](std::shared_ptr client, const std::string& topic, const std::string& payload) { + if (topic.find("Availability") != std::string::npos) { + lastWillReceived = true; + } + }; + + createClient(options, std::move(handler)); + ASSERT_TRUE(startClient()); + m_client->subscribe("MTConnect/Probe/#"); + + createAgent(); + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); + auto mqttSink = std::dynamic_pointer_cast(sink); + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); + + m_client->stop(); + ASSERT_TRUE(waitFor(5s, [&lastWillReceived]() { return lastWillReceived; })); +} From bb5953b16e5f4898fbf4e85e4a75a1e0039136cb Mon Sep 17 00:00:00 2001 From: virajdere Date: Fri, 24 Oct 2025 21:58:12 +0000 Subject: [PATCH 24/84] removed duplicate entry --- agent_lib/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 343a2c1c8..780bb73d8 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -256,14 +256,6 @@ set(AGENT_SOURCES #src/sink/mqtt_sink SOURCE_FILES_ONLY "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.cpp" - -# src/sink/mqtt_sink HEADER_FILE_ONLY - - "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.hpp" - -#src/sink/mqtt_sink SOURCE_FILES_ONLY - - "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.cpp" # src/sink/mqtt_entity_sink HEADER_FILE_ONLY From 31aa5160e40ab9c69bcb5ef14d3ef88543bf0b27 Mon Sep 17 00:00:00 2001 From: virajdere Date: Sat, 25 Oct 2025 05:35:23 +0000 Subject: [PATCH 25/84] updated cmake for entity_sink to use /bigobj for windows --- agent_lib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 780bb73d8..4e979e0c1 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -349,6 +349,7 @@ if(MSVC) # The modules including Beast required the /bigobj option in Windows set_property(SOURCE "${SOURCE_DIR}/sink/mqtt_sink/mqtt_service.cpp" + "${SOURCE_DIR}/sink/mqtt_entity_sink/mqtt_entity_sink.cpp" "${SOURCE_DIR}/sink/rest_sink/session_impl.cpp" "${SOURCE_DIR}/source/adapter/mqtt/mqtt_adapter.cpp" "${SOURCE_DIR}/source/adapter/agent_adapter/agent_adapter.cpp" From a8f5ba88447f574f8dcea9bf26ce2def0a886cec Mon Sep 17 00:00:00 2001 From: virajdere Date: Sat, 25 Oct 2025 17:16:44 +0000 Subject: [PATCH 26/84] Changed copyright from 2009 to 2025 --- src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp | 2 +- src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp index 8c288919a..c826d0871 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2023, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp index 34e248ad6..76586a92f 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp @@ -1,5 +1,5 @@ // -// Copyright Copyright 2009-2023, AMT – The Association For Manufacturing Technology (“AMT”) +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); From b6745137b5466df7a74e7b950a840b3973bc2438 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 13:17:51 +0100 Subject: [PATCH 27/84] Added fatal excetion handling and graceful shutdown of the agent. --- src/mtconnect/agent.cpp | 591 +++++++++--------- src/mtconnect/configuration/agent_config.cpp | 12 +- src/mtconnect/configuration/agent_config.hpp | 123 ++-- src/mtconnect/configuration/async_context.hpp | 170 +++-- src/mtconnect/configuration/service.cpp | 13 +- src/mtconnect/configuration/service.hpp | 2 +- src/mtconnect/device_model/agent_device.cpp | 1 + .../device_model/data_item/data_item.cpp | 2 +- src/mtconnect/device_model/device.cpp | 12 +- src/mtconnect/entity/factory.cpp | 2 +- src/mtconnect/observation/observation.cpp | 20 +- src/mtconnect/parser/xml_parser.cpp | 8 +- src/mtconnect/ruby/embedded.cpp | 6 +- src/mtconnect/sink/rest_sink/file_cache.cpp | 4 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 4 +- src/mtconnect/sink/rest_sink/server.cpp | 136 ++-- .../adapter/agent_adapter/agent_adapter.cpp | 5 +- .../adapter/agent_adapter/session_impl.hpp | 213 ++++--- .../source/adapter/mqtt/mqtt_adapter.cpp | 6 +- src/mtconnect/utilities.hpp | 317 +++++----- test_package/agent_test_helper.cpp | 4 +- test_package/file_cache_test.cpp | 4 +- 22 files changed, 875 insertions(+), 780 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 8efbb565c..fee9145e2 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -73,26 +73,26 @@ namespace mtconnect { namespace net = boost::asio; namespace fs = boost::filesystem; namespace config = mtconnect::configuration; - + static const string g_unavailable("UNAVAILABLE"); static const string g_available("AVAILABLE"); - + // Agent public methods Agent::Agent(config::AsyncContext &context, const string &deviceXmlPath, const ConfigOptions &options) - : m_options(options), - m_context(context), - m_strand(m_context), - m_xmlParser(make_unique()), - m_schemaVersion(GetOption(options, config::SchemaVersion)), - m_deviceXmlPath(deviceXmlPath), - m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), - GetOption(options, config::CheckpointFrequency).value_or(1000)), - m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), - m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) + : m_options(options), + m_context(context), + m_strand(m_context), + m_xmlParser(make_unique()), + m_schemaVersion(GetOption(options, config::SchemaVersion)), + m_deviceXmlPath(deviceXmlPath), + m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), + GetOption(options, config::CheckpointFrequency).value_or(1000)), + m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), + m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) { using namespace asset; - + CuttingToolArchetype::registerAsset(); CuttingTool::registerAsset(); FileArchetypeAsset::registerAsset(); @@ -102,26 +102,26 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); - + m_assetStorage = make_unique( - GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); + GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); m_versionDeviceXml = IsOptionSet(options, mtconnect::configuration::VersionDeviceXml); m_createUniqueIds = IsOptionSet(options, config::CreateUniqueIds); - + auto jsonVersion = - uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); - + uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); + // Create the Printers m_printers["xml"] = make_unique(m_pretty, m_validation); m_printers["json"] = make_unique(jsonVersion, m_pretty, m_validation); - + if (m_schemaVersion) { m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); } - + auto sender = GetOption(options, config::Sender); if (sender) { @@ -129,51 +129,51 @@ namespace mtconnect { pr->setSenderName(*sender); } } - + void Agent::initialize(pipeline::PipelineContextPtr context) { NAMED_SCOPE("Agent::initialize"); - + m_beforeInitializeHooks.exec(*this); - + m_pipelineContext = context; m_loopback = - std::make_shared("AgentSource", m_strand, context, m_options); - + std::make_shared("AgentSource", m_strand, context, m_options); + auto devices = loadXMLDeviceFile(m_deviceXmlPath); if (!m_schemaVersion) { m_schemaVersion.emplace(StrDefaultSchemaVersion()); } - + m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); - + auto disableAgentDevice = GetOption(m_options, config::DisableAgentDevice); if (!(disableAgentDevice && *disableAgentDevice) && m_intSchemaVersion >= SCHEMA_VERSION(1, 7)) { createAgentDevice(); } - + // For the DeviceAdded event for each device for (auto device : devices) addDevice(device); - + if (m_versionDeviceXml && m_createUniqueIds) versionDeviceXml(); - + loadCachedProbe(); - + m_initialized = true; - + m_afterInitializeHooks.exec(*this); } - + void Agent::initialDataItemObservations() { NAMED_SCOPE("Agent::initialDataItemObservations"); - + if (!m_observationsInitialized) { if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && @@ -183,17 +183,17 @@ namespace mtconnect { for (auto &printer : m_printers) printer.second->setValidation(false); } - + for (auto device : m_deviceIndex) initializeDataItems(device); - + if (m_agentDevice) { for (auto device : m_deviceIndex) { auto d = m_agentDevice->getDeviceDataItem("device_added"); string uuid = *device->getUuid(); - + entity::Properties props {{"VALUE", uuid}}; if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { @@ -201,15 +201,15 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + m_loopback->receive(d, props); } } - + m_observationsInitialized = true; } } - + Agent::~Agent() { m_xmlParser.reset(); @@ -217,64 +217,64 @@ namespace mtconnect { m_sources.clear(); m_agentDevice = nullptr; } - + void Agent::start() { NAMED_SCOPE("Agent::start"); - + if (m_started) { LOG(warning) << "Agent already started."; return; } - + try { m_beforeStartHooks.exec(*this); - + for (auto sink : m_sinks) sink->start(); - + initialDataItemObservations(); - + if (m_agentDevice) { auto d = m_agentDevice->getDeviceDataItem("agent_avail"); m_loopback->receive(d, "AVAILABLE"s); } - + // Start all the sources for (auto source : m_sources) source->start(); - + m_afterStartHooks.exec(*this); } catch (std::runtime_error &e) { LOG(fatal) << "Cannot start server: " << e.what(); - std::exit(1); + throw FatalException(e.what()); } - + m_started = true; } - + void Agent::stop() { NAMED_SCOPE("Agent::stop"); - + if (!m_started) { LOG(warning) << "Agent already stopped."; return; } - + m_beforeStopHooks.exec(*this); - + // Stop all adapter threads... LOG(info) << "Shutting down sources"; for (auto source : m_sources) source->stop(); - + // Signal all observers LOG(info) << "Signaling observers to close sessions"; for (auto di : m_dataItemMap) @@ -283,16 +283,16 @@ namespace mtconnect { if (ldi) ldi->signalObservers(0); } - + LOG(info) << "Shutting down sinks"; for (auto sink : m_sinks) sink->stop(); - + LOG(info) << "Shutting down completed"; - + m_started = false; } - + // --------------------------------------- // Pipeline methods // --------------------------------------- @@ -307,7 +307,7 @@ namespace mtconnect { { if (item.expired()) continue; - + auto di = item.lock(); if (di->hasInitialValue()) { @@ -315,7 +315,7 @@ namespace mtconnect { } } } - + std::lock_guard lock(m_circularBuffer); if (m_circularBuffer.addToBuffer(observation) != 0) { @@ -323,7 +323,7 @@ namespace mtconnect { sink->publish(observation); } } - + void Agent::receiveAsset(asset::AssetPtr asset) { DevicePtr device; @@ -332,14 +332,14 @@ namespace mtconnect { device = findDeviceByUUIDorName(*uuid); else device = getDefaultDevice(); - + if (device && device->getAssetChanged() && device->getAssetRemoved()) { if (asset->getDeviceUuid() && *asset->getDeviceUuid() != *device->getUuid()) { asset->setProperty("deviceUuid", *device->getUuid()); } - + string aid = asset->getAssetId(); if (aid[0] == '@') { @@ -353,16 +353,16 @@ namespace mtconnect { asset->setAssetId(aid); } } - + // Add hash to asset if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) asset->addHash(); - + auto old = m_assetStorage->addAsset(asset); - + for (auto &sink : m_sinks) sink->publish(asset); - + if (device) { DataItemPtr di; @@ -388,7 +388,7 @@ namespace mtconnect { if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { const auto &hash = asset->getProperty("hash"); @@ -397,22 +397,22 @@ namespace mtconnect { props.insert_or_assign("hash", hash); } } - + m_loopback->receive(di, props); } - + updateAssetCounts(device, asset->getType()); } } - + bool Agent::reloadDevices(const std::string &deviceFile) { try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - deviceFile, dynamic_cast(m_printers["xml"].get())); - + deviceFile, dynamic_cast(m_printers["xml"].get())); + if (m_xmlParser->getSchemaVersion() && IntSchemaVersion(*m_xmlParser->getSchemaVersion()) != m_intSchemaVersion) { @@ -420,7 +420,7 @@ namespace mtconnect { LOG(warning) << "Schema version does not match agent schema version, restarting the agent"; return false; } - + bool changed = false; for (auto device : devices) { @@ -428,7 +428,7 @@ namespace mtconnect { } if (changed) loadCachedProbe(); - + return true; } catch (runtime_error &e) @@ -436,17 +436,17 @@ namespace mtconnect { LOG(fatal) << "Error loading xml configuration: " + deviceFile; LOG(fatal) << "Error detail: " << e.what(); cerr << e.what() << endl; - throw e; + throw FatalException(e.what()); } catch (exception &f) { LOG(fatal) << "Error loading xml configuration: " + deviceFile; LOG(fatal) << "Error detail: " << f.what(); cerr << f.what() << endl; - throw f; + throw FatalException(f.what()); } } - + void Agent::loadDeviceXml(const string &deviceXml, const optional source) { try @@ -468,7 +468,7 @@ namespace mtconnect { cerr << f.what() << endl; } } - + void Agent::loadDevices(list devices, const optional source, bool force) { if (!force && !IsOptionSet(m_options, config::EnableSourceDeviceModels)) @@ -476,7 +476,7 @@ namespace mtconnect { LOG(warning) << "Device updates are disabled, skipping update"; return; } - + auto callback = [=, this](config::AsyncContext &context) { try { @@ -490,10 +490,10 @@ namespace mtconnect { { oldUuid = *oldDev->getUuid(); } - + auto uuid = *device->getUuid(); auto name = *device->getComponentName(); - + changed = receiveDevice(device, true) || changed; if (changed) { @@ -505,7 +505,7 @@ namespace mtconnect { s->setOptions({{config::Device, uuid}}); } } - + for (auto src : m_sources) { auto adapter = std::dynamic_pointer_cast(src); @@ -521,10 +521,14 @@ namespace mtconnect { } } } - + if (changed) loadCachedProbe(); } + catch (FatalException &e) + { + throw e; + } catch (runtime_error &e) { for (auto device : devices) @@ -544,7 +548,7 @@ namespace mtconnect { cerr << f.what() << endl; } }; - + // Gets around a race condition in the loading of adapaters and setting of // UUID. if (m_context.isRunning() && !m_context.isPauased()) @@ -552,11 +556,11 @@ namespace mtconnect { else callback(m_context); } - + bool Agent::receiveDevice(device_model::DevicePtr device, bool version) { NAMED_SCOPE("Agent::receiveDevice"); - + // diff the device against the current device with the same uuid auto uuid = device->getUuid(); if (!uuid) @@ -564,7 +568,7 @@ namespace mtconnect { LOG(error) << "Device does not have a uuid: " << device->getName(); return false; } - + DevicePtr oldDev = findDeviceByUUIDorName(*uuid); if (!oldDev) { @@ -574,10 +578,10 @@ namespace mtconnect { LOG(error) << "Device does not have a name" << *uuid; return false; } - + oldDev = findDeviceByUUIDorName(*name); } - + // If this is a new device if (!oldDev) { @@ -585,10 +589,10 @@ namespace mtconnect { addDevice(device); if (version) versionDeviceXml(); - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -599,15 +603,15 @@ namespace mtconnect { LOG(error) << "Device does not have a name: " << *device->getUuid(); return false; } - + verifyDevice(device); createUniqueIds(device); - + LOG(info) << "Checking if device " << *uuid << " has changed"; if (device->different(*oldDev)) { LOG(info) << "Device " << *uuid << " changed, updating model"; - + // Remove the old data items set skip; for (auto &di : oldDev->getDeviceDataItems()) @@ -618,7 +622,7 @@ namespace mtconnect { skip.insert(di.lock()->getId()); } } - + // Replace device in device maps auto it = find(m_deviceIndex.begin(), m_deviceIndex.end(), oldDev); if (it != m_deviceIndex.end()) @@ -628,18 +632,18 @@ namespace mtconnect { LOG(error) << "Cannot find Device " << *uuid << " in devices"; return false; } - + initializeDataItems(device, skip); - + LOG(info) << "Device " << *uuid << " updating circular buffer"; m_circularBuffer.updateDataItems(m_dataItemMap); - + if (m_intSchemaVersion > SCHEMA_VERSION(2, 2)) device->addHash(); - + if (version) versionDeviceXml(); - + if (m_agentDevice) { entity::Properties props {{"VALUE", *uuid}}; @@ -649,14 +653,14 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_changed"); m_loopback->receive(d, props); } - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -664,29 +668,29 @@ namespace mtconnect { LOG(info) << "Device " << *uuid << " did not change, ignoring new device"; } } - + return false; } - + void Agent::versionDeviceXml() { NAMED_SCOPE("Agent::versionDeviceXml"); - + using namespace std::chrono; - + if (m_versionDeviceXml) { m_beforeDeviceXmlUpdateHooks.exec(*this); - + // update with a new version of the device.xml, saving the old one // with a date time stamp auto ext = - date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); + date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); fs::path file(m_deviceXmlPath); fs::path backup(m_deviceXmlPath + ext); if (!fs::exists(backup)) fs::rename(file, backup); - + auto printer = getPrinter("xml"); if (printer != nullptr) { @@ -694,11 +698,11 @@ namespace mtconnect { copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list), [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; }); auto probe = printer->printProbe(0, 0, 0, 0, 0, list, nullptr, true, true); - + ofstream devices(file.string()); devices << probe; devices.close(); - + m_afterDeviceXmlUpdateHooks.exec(*this); } else @@ -707,7 +711,7 @@ namespace mtconnect { } } } - + bool Agent::removeAsset(DevicePtr device, const std::string &id, const std::optional time) { @@ -716,10 +720,10 @@ namespace mtconnect { { for (auto &sink : m_sinks) sink->publish(asset); - + notifyAssetRemoved(device, asset); updateAssetCounts(device, asset->getType()); - + return true; } else @@ -727,7 +731,7 @@ namespace mtconnect { return false; } } - + bool Agent::removeAllAssets(const std::optional device, const std::optional type, const std::optional time, asset::AssetList &list) @@ -742,13 +746,13 @@ namespace mtconnect { else uuid = device; } - + auto count = m_assetStorage->removeAll(list, uuid, type, time); for (auto &asset : list) { notifyAssetRemoved(nullptr, asset); } - + if (dev) { updateAssetCounts(dev, type); @@ -758,10 +762,10 @@ namespace mtconnect { for (auto d : m_deviceIndex) updateAssetCounts(d, type); } - + return count > 0; } - + void Agent::notifyAssetRemoved(DevicePtr device, const asset::AssetPtr &asset) { if (device || asset->getDeviceUuid()) @@ -787,7 +791,7 @@ namespace mtconnect { {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); } } - + auto added = dev->getAssetAdded(); if (added) { @@ -800,57 +804,57 @@ namespace mtconnect { } } } - + // --------------------------------------- // Agent Device // --------------------------------------- - + void Agent::createAgentDevice() { NAMED_SCOPE("Agent::createAgentDevice"); - + using namespace boost; - + auto address = GetBestHostAddress(m_context); - + auto port = GetOption(m_options, mtconnect::configuration::Port).value_or(5000); auto service = boost::lexical_cast(port); address.append(":").append(service); - + uuids::name_generator_latest gen(uuids::ns::dns()); auto uuid = GetOption(m_options, mtconnect::configuration::AgentDeviceUUID) - .value_or(uuids::to_string(gen(address))); + .value_or(uuids::to_string(gen(address))); auto id = "agent_"s + uuid.substr(0, uuid.find_first_of('-')); - + // Create the Agent Device ErrorList errors; Properties ps { - {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; + {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; m_agentDevice = - dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); + dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); if (!errors.empty()) { for (auto &e : errors) LOG(fatal) << "Error creating the agent device: " << e->what(); - throw EntityError("Cannot create AgentDevice"); + throw FatalException("Cannot create AgentDevice"); } addDevice(m_agentDevice); } - + // ---------------------------------------------- // Device management and Initialization // ---------------------------------------------- - + std::list Agent::loadXMLDeviceFile(const std::string &configXmlPath) { NAMED_SCOPE("Agent::loadXMLDeviceFile"); - + try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - configXmlPath, dynamic_cast(m_printers["xml"].get())); - + configXmlPath, dynamic_cast(m_printers["xml"].get())); + if (!m_schemaVersion && m_xmlParser->getSchemaVersion() && !m_xmlParser->getSchemaVersion()->empty()) { @@ -862,7 +866,7 @@ namespace mtconnect { m_schemaVersion = StrDefaultSchemaVersion(); m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); } - + return devices; } catch (runtime_error &e) @@ -870,23 +874,23 @@ namespace mtconnect { LOG(fatal) << "Error loading xml configuration: " + configXmlPath; LOG(fatal) << "Error detail: " << e.what(); cerr << e.what() << endl; - throw e; + throw FatalException(e.what()); } catch (exception &f) { LOG(fatal) << "Error loading xml configuration: " + configXmlPath; LOG(fatal) << "Error detail: " << f.what(); cerr << f.what() << endl; - throw f; + throw FatalException(f.what()); } - + return {}; } - + void Agent::verifyDevice(DevicePtr device) { NAMED_SCOPE("Agent::verifyDevice"); - + // Add the devices to the device map and create availability and // asset changed events if they don't exist // Make sure we have two device level data items: @@ -897,88 +901,92 @@ namespace mtconnect { // Create availability data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make( - {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, - errors); + {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, + errors); device->addDataItem(di, errors); } - + if (!device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 2)) { entity::ErrorList errors; // Create asset change data item and add it to the device. auto di = DataItem::make({{"type", "ASSET_CHANGED"s}, - {"id", device->getId() + "_asset_chg"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_chg"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 5)) { auto di = device->getAssetChanged(); if (!di->isDiscrete()) di->makeDiscrete(); } - + if (!device->getAssetRemoved() && m_intSchemaVersion >= SCHEMA_VERSION(1, 3)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_REMOVED"s}, - {"id", device->getId() + "_asset_rem"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_rem"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetAdded() && m_intSchemaVersion >= SCHEMA_VERSION(2, 6)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_ADDED"s}, - {"id", device->getId() + "_asset_add"}, - {"discrete", true}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_add"}, + {"discrete", true}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetCount() && m_intSchemaVersion >= SCHEMA_VERSION(2, 0)) { entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_COUNT"s}, - {"id", device->getId() + "_asset_count"}, - {"category", "EVENT"s}, - {"representation", "DATA_SET"s}}, + {"id", device->getId() + "_asset_count"}, + {"category", "EVENT"s}, + {"representation", "DATA_SET"s}}, errors); device->addDataItem(di, errors); } - + if (IsOptionSet(m_options, mtconnect::configuration::PreserveUUID)) { device->setPreserveUuid(*GetOption(m_options, mtconnect::configuration::PreserveUUID)); } } - + void Agent::initializeDataItems(DevicePtr device, std::optional> skip) { NAMED_SCOPE("Agent::initializeDataItems"); - + // Initialize the id mapping for the devices and set all data items to UNAVAILABLE for (auto item : device->getDeviceDataItems()) { if (item.expired()) continue; - + auto d = item.lock(); if ((!skip || skip->count(d->getId()) > 0) && m_dataItemMap.count(d->getId()) > 0) { auto di = m_dataItemMap[d->getId()].lock(); if (di && di != d) { - LOG(fatal) << "Duplicate DataItem id " << d->getId() - << " for device: " << *device->getComponentName(); - std::exit(1); + stringstream msg; + + msg << "Duplicate DataItem id " << d->getId() + << " for device: " << *device->getComponentName() << + ". Try using configuration option CreateUniqueIds to resolve."; + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } else @@ -989,18 +997,18 @@ namespace mtconnect { value = &g_unavailable; else if (d->getConstantValue()) value = &d->getConstantValue().value(); - + m_loopback->receive(d, *value); m_dataItemMap[d->getId()] = d; } } } - + // Add the a device from a configuration file void Agent::addDevice(DevicePtr device) { NAMED_SCOPE("Agent::addDevice"); - + // Check if device already exists string uuid = *device->getUuid(); auto &idx = m_deviceIndex.get(); @@ -1008,23 +1016,24 @@ namespace mtconnect { if (old != idx.end()) { // Update existing device - LOG(fatal) << "Device " << *device->getUuid() << " already exists. " - << " Update not supported yet"; - std::exit(1); + stringstream msg; + msg << "Device " << *device->getUuid() << " already exists. " + << " Update not supported yet"; + throw msg; } else { m_deviceIndex.push_back(device); - + // TODO: Redo Resolve Reference with entity // device->resolveReferences(); verifyDevice(device); createUniqueIds(device); - + if (m_observationsInitialized) { initializeDataItems(device); - + // Check for single valued constrained data items. if (m_agentDevice && device != m_agentDevice) { @@ -1035,24 +1044,24 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_added"); m_loopback->receive(d, props); } } } - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) device->addHash(); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + void Agent::deviceChanged(DevicePtr device, const std::string &uuid) { NAMED_SCOPE("Agent::deviceChanged"); - + bool changed = false; string oldUuid = *device->getUuid(); if (uuid != oldUuid) @@ -1065,28 +1074,28 @@ namespace mtconnect { m_loopback->receive(d, oldUuid); } } - + if (changed) { // Create a new device auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); auto newDevice = m_xmlParser->parseDevice(xmlPrinter->printDevice(device), xmlPrinter); - + newDevice->setUuid(uuid); - + m_loopback->receive(newDevice); } } - + void Agent::createUniqueIds(DevicePtr device) { if (m_createUniqueIds && !dynamic_pointer_cast(device)) { std::unordered_map idMap; - + device->createUniqueIds(idMap); device->updateReferences(idMap); - + // Update the data item map. for (auto &id : idMap) { @@ -1099,83 +1108,83 @@ namespace mtconnect { } } } - + void Agent::loadCachedProbe() { NAMED_SCOPE("Agent::loadCachedProbe"); - + // Reload the document for path resolution auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); m_xmlParser->loadDocument(xmlPrinter->printProbe(0, 0, 0, 0, 0, getDevices())); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + // ---------------------------------------------------- // Helper Methods // ---------------------------------------------------- - + DevicePtr Agent::getDeviceByName(const std::string &name) const { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::getDeviceByName(const std::string &name) { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::findDeviceByUUIDorName(const std::string &idOrName) const { if (idOrName.empty()) return getDefaultDevice(); - + DevicePtr res; if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; else if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; - + return res; } - + // ---------------------------------------------------- // Adapter Methods // ---------------------------------------------------- - + void Agent::addSource(source::SourcePtr source, bool start) { m_sources.emplace_back(source); - + if (start) source->start(); - + auto adapter = dynamic_pointer_cast(source); if (m_agentDevice && adapter) { m_agentDevice->addAdapter(adapter); - + if (m_observationsInitialized) initializeDataItems(m_agentDevice); - + // Reload the document for path resolution if (m_initialized) { @@ -1183,15 +1192,15 @@ namespace mtconnect { } } } - + void Agent::addSink(sink::SinkPtr sink, bool start) { m_sinks.emplace_back(sink); - + if (start) sink->start(); } - + void AgentPipelineContract::deliverConnectStatus(entity::EntityPtr entity, const StringList &devices, bool autoAvailable) { @@ -1213,15 +1222,15 @@ namespace mtconnect { LOG(error) << "Unexpected connection status received: " << value; } } - + void AgentPipelineContract::deliverCommand(entity::EntityPtr entity) { auto command = entity->get("command"); auto value = entity->getValue(); - + auto device = entity->maybeGet("device"); auto source = entity->maybeGet("source"); - + if ((command != "devicemodel" && !device) || !source) { LOG(error) << "Invalid command: " << command << ", device or source not specified"; @@ -1234,7 +1243,7 @@ namespace mtconnect { m_agent->receiveCommand(*device, command, value, *source); } } - + void Agent::connecting(const std::string &adapter) { if (m_agentDevice) @@ -1244,30 +1253,30 @@ namespace mtconnect { m_loopback->receive(di, "LISTEN"); } } - + // Add values for related data items UNAVAILABLE void Agent::disconnected(const std::string &adapter, const StringList &devices, bool autoAvailable) { LOG(debug) << "Disconnected from adapter, setting all values to UNAVAILABLE"; - + if (m_agentDevice) { auto di = m_agentDevice->getConnectionStatus(adapter); if (di) m_loopback->receive(di, "CLOSED"); } - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); if (device == nullptr) { LOG(warning) << "Cannot find device " << name << " when adapter " << adapter - << "disconnected"; + << "disconnected"; continue; } - + for (auto di : device->getDeviceDataItems()) { auto dataItem = di.lock(); @@ -1276,7 +1285,7 @@ namespace mtconnect { dataItem->getType() == "AVAILABILITY"))) { auto ptr = getLatest(dataItem->getId()); - + if (ptr) { const string *value = nullptr; @@ -1284,7 +1293,7 @@ namespace mtconnect { value = &dataItem->getConstantValue().value(); else if (!ptr->isUnavailable()) value = &g_unavailable; - + if (value) m_loopback->receive(dataItem, *value); } @@ -1294,7 +1303,7 @@ namespace mtconnect { } } } - + void Agent::connected(const std::string &adapter, const StringList &devices, bool autoAvailable) { if (m_agentDevice) @@ -1303,10 +1312,10 @@ namespace mtconnect { if (di) m_loopback->receive(di, "ESTABLISHED"); } - + if (!autoAvailable) return; - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); @@ -1316,7 +1325,7 @@ namespace mtconnect { continue; } LOG(debug) << "Connected to adapter, setting all Availability data items to AVAILABLE"; - + if (auto avail = device->getAvailability()) { LOG(debug) << "Adding availabilty event for " << device->getAvailability()->getId(); @@ -1326,7 +1335,7 @@ namespace mtconnect { LOG(debug) << "Cannot find availability for " << *device->getComponentName(); } } - + void Agent::sourceFailed(const std::string &identity) { auto source = findSource(identity); @@ -1334,7 +1343,7 @@ namespace mtconnect { { source->stop(); m_sources.remove(source); - + bool ext = false; for (auto &s : m_sources) { @@ -1344,7 +1353,7 @@ namespace mtconnect { break; } } - + if (!ext) { LOG(fatal) << "Source " << source->getName() << " failed"; @@ -1362,16 +1371,16 @@ namespace mtconnect { LOG(error) << "Cannot find failed source: " << source->getName(); } } - + // ----------------------------------------------- // Validation methods // ----------------------------------------------- - + string Agent::devicesAndPath(const std::optional &path, const DevicePtr device, const std::optional &deviceType) const { string dataPath; - + if (device || deviceType) { string prefix; @@ -1381,16 +1390,16 @@ namespace mtconnect { prefix = "//Devices/Device[@uuid=\"" + *device->getUuid() + "\"]"; else if (deviceType) prefix = "//Devices/Device"; - + if (path) { stringstream parse(*path); string token; - + // Prefix path (i.e. "path1|path2" => "{prefix}path1|{prefix}path2") while (getline(parse, token, '|')) dataPath += prefix + token + "|"; - + dataPath.erase(dataPath.length() - 1); } else @@ -1405,10 +1414,10 @@ namespace mtconnect { else dataPath = "//Devices/Device|//Devices/Agent"; } - + return dataPath; } - + void AgentPipelineContract::deliverAssetCommand(entity::EntityPtr command) { const std::string &cmd = command->getValue(); @@ -1433,33 +1442,33 @@ namespace mtconnect { LOG(error) << "Invalid assent command: " << cmd; } } - + void Agent::updateAssetCounts(const DevicePtr &device, const std::optional type) { if (!device) return; - + auto dc = device->getAssetCount(); if (dc) { if (type) { auto count = m_assetStorage->getCountForDeviceAndType(*device->getUuid(), *type); - + DataSet set; if (count > 0) set.emplace(*type, int64_t(count)); else set.emplace(*type, DataSetValue(), true); - + m_loopback->receive(dc, {{"VALUE", set}}); } else { auto counts = m_assetStorage->getCountsByTypeForDevice(*device->getUuid()); - + DataSet set; - + for (auto &[t, count] : counts) { if (count > 0) @@ -1467,67 +1476,67 @@ namespace mtconnect { else set.emplace(t, DataSetValue(), true); } - + m_loopback->receive(dc, {{"resetTriggered", "RESET_COUNTS"s}, {"VALUE", set}}); } } } - + void Agent::receiveCommand(const std::string &deviceName, const std::string &command, const std::string &value, const std::string &source) { DevicePtr device {nullptr}; device = findDeviceByUUIDorName(deviceName); - + if (!device) { LOG(warning) << source << ": Cannot find device for name " << deviceName; } - + static std::unordered_map> - deviceCommands { - {"manufacturer", mem_fn(&Device::setManufacturer)}, - {"station", mem_fn(&Device::setStation)}, - {"serialnumber", mem_fn(&Device::setSerialNumber)}, - {"description", mem_fn(&Device::setDescriptionValue)}, - {"nativename", - [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, - {"calibration", [](DevicePtr device, const string &value) { - istringstream line(value); - - // Look for name|factor|offset triples - string name, factor, offset; - while (getline(line, name, '|') && getline(line, factor, '|') && - getline(line, offset, '|')) - { - // Convert to a floating point number - auto di = device->getDeviceDataItem(name); - if (!di) - LOG(warning) << "Cannot find data item to calibrate for " << name; - else - { - try - { - double fact_value = stod(factor); - double off_value = stod(offset); - - device_model::data_item::UnitConversion conv(fact_value, off_value); - di->setConverter(conv); - } - catch (std::exception e) - { - LOG(error) << "Cannot convert factor " << factor << " or " << offset - << " to double: " << e.what(); - } - } - } - }}}; - + deviceCommands { + {"manufacturer", mem_fn(&Device::setManufacturer)}, + {"station", mem_fn(&Device::setStation)}, + {"serialnumber", mem_fn(&Device::setSerialNumber)}, + {"description", mem_fn(&Device::setDescriptionValue)}, + {"nativename", + [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, + {"calibration", [](DevicePtr device, const string &value) { + istringstream line(value); + + // Look for name|factor|offset triples + string name, factor, offset; + while (getline(line, name, '|') && getline(line, factor, '|') && + getline(line, offset, '|')) + { + // Convert to a floating point number + auto di = device->getDeviceDataItem(name); + if (!di) + LOG(warning) << "Cannot find data item to calibrate for " << name; + else + { + try + { + double fact_value = stod(factor); + double off_value = stod(offset); + + device_model::data_item::UnitConversion conv(fact_value, off_value); + di->setConverter(conv); + } + catch (std::exception e) + { + LOG(error) << "Cannot convert factor " << factor << " or " << offset + << " to double: " << e.what(); + } + } + } + }}}; + static std::unordered_map adapterDataItems { - {"adapterversion", "_adapter_software_version"}, - {"mtconnectversion", "_mtconnect_version"}, + {"adapterversion", "_adapter_software_version"}, + {"mtconnectversion", "_mtconnect_version"}, }; - + if (command == "devicemodel") { loadDeviceXml(value, source); @@ -1569,7 +1578,7 @@ namespace mtconnect { else { LOG(warning) << "Cannot find data item for the Agent device when processing command " - << command << " with value " << value << " for adapter " << source; + << command << " with value " << value << " for adapter " << source; } } } @@ -1583,10 +1592,10 @@ namespace mtconnect { else { LOG(error) << source << ":Received protocol command '" << command << "' for device '" - << deviceName << "', but the device could not be found"; + << deviceName << "', but the device could not be found"; } } - + // ------------------------------------------- // End // ------------------------------------------- diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 48ee6f8b2..a42f93f9d 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -182,12 +182,12 @@ namespace mtconnect::configuration { buffer << file.rdbuf(); FileFormat fmt = MTCONNECT; - if (ends_with(m_configFile.string(), "json")) + if (m_configFile.string().ends_with("json")) { LOG(debug) << "Parsing json configuration"; fmt = JSON; } - else if (ends_with(m_configFile.string(), "xml")) + else if (m_configFile.string().ends_with("xml")) { LOG(debug) << "Parsing xml configuration"; fmt = XML; @@ -392,7 +392,7 @@ namespace mtconnect::configuration { m_monitorTimer.async_wait(boost::bind(&AgentConfiguration::monitorFiles, this, _1)); } - void AgentConfiguration::start() + int AgentConfiguration::start() { if (m_monitorFiles) { @@ -411,7 +411,7 @@ namespace mtconnect::configuration { m_context->setThreadCount(m_workerThreadCount); m_beforeStartHooks.exec(*this); m_agent->start(); - m_context->start(); + return m_context->start(); } void AgentConfiguration::stop() @@ -879,7 +879,7 @@ namespace mtconnect::configuration { LOG(fatal) << "Cannot find device configuration file"; logPaths(LOG_LEVEL(fatal), m_configPaths); - throw runtime_error(((string) "Please make sure the configuration " + throw FatalException(((string) "Please make sure the configuration " "file probe.xml or Devices.xml is in the current " "directory or specify the correct file " "in the configuration file " + @@ -958,7 +958,7 @@ namespace mtconnect::configuration { if (host.empty()) { LOG(fatal) << "Malformed URL in configuration file: '" << url << "', exiting"; - exit(1); + throw FatalException(); } options[configuration::Host] = host; diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 72191af28..347123fa1 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -47,7 +47,7 @@ namespace mtconnect { namespace device_model { class Device; } - + #ifdef WITH_PYTHON namespace python { class Embedded; @@ -58,13 +58,13 @@ namespace mtconnect { class Embedded; } #endif - + class XmlPrinter; - + /// @brief Configuration namespace namespace configuration { using DevicePtr = std::shared_ptr; - + /// @brief Parses the configuration file and creates the `Agent`. Manages config /// file tracking and restarting of the agent. class AGENT_LIB_API AgentConfiguration : public MTConnectService @@ -77,19 +77,19 @@ namespace mtconnect { XML, UNKNOWN }; - + using InitializationFn = void(const boost::property_tree::ptree &, AgentConfiguration &); using InitializationFunction = boost::function; - + using ptree = boost::property_tree::ptree; - + /// @brief Construct the agent configuration AgentConfiguration(); virtual ~AgentConfiguration(); - + /// @name Callbacks for initialization phases ///@{ - + /// @brief Get the callback manager after the agent is created /// @return the callback manager auto &afterAgentHooks() { return m_afterAgentHooks; } @@ -100,32 +100,33 @@ namespace mtconnect { /// @return the callback manager auto &beforeStopHooks() { return m_beforeStopHooks; } ///@} - + /// @brief stops the agent. Used in daemons. void stop() override; /// @brief starts the agent. Used in daemons. - void start() override; + /// @return 0 on success + int start() override; /// @brief initializes the configuration of the agent from the command line parameters /// @param[in] options command line parameters void initialize(const boost::program_options::variables_map &options) override; - + /// @brief Configure the logger with the config node from the config file /// @param channelName the log channel name /// @param config the configuration node /// @param formatter optional custom message format void configureLoggerChannel( - const std::string &channelName, const ptree &config, - std::optional> formatter = std::nullopt); - + const std::string &channelName, const ptree &config, + std::optional> formatter = std::nullopt); + /// @brief Configure the agent logger with the config node from the config file /// @param config the configuration node void configureLogger(const ptree &config); - + /// @brief load a configuration text /// @param[in] text the configuration text loaded from a file /// @param[in] fmt the file format, can be MTCONNECT, JSON, or XML void loadConfig(const std::string &text, FileFormat fmt = MTCONNECT); - + /// @brief assign the agent associated with this configuration /// @param[in] agent the agent the configuration will take ownership of void setAgent(std::unique_ptr &agent) { m_agent = std::move(agent); } @@ -135,10 +136,10 @@ namespace mtconnect { auto &getContext() { return m_context->get(); } /// @brief get a pointer to the async io manager auto &getAsyncContext() { return *m_context.get(); } - + /// @brief sets the path for the working directory to the current path void updateWorkingDirectory() { m_working = std::filesystem::current_path(); } - + /// @name Configuration factories ///@{ /// @brief get the factory for creating sinks @@ -148,11 +149,11 @@ namespace mtconnect { /// @return the factory auto &getSourceFactory() { return m_sourceFactory; } ///@} - + /// @brief get the pipeline context for this configuration /// @note set after the agent is created auto getPipelineContext() { return m_pipelineContext; } - + /// @name Logging methods ///@{ /// @brief gets the boost log sink @@ -209,7 +210,7 @@ namespace mtconnect { { return m_logChannels[channelName].m_logLevel; } - + /// @brief set the logging level /// @param[in] level the new logging level void setLoggingLevel(const boost::log::trivial::severity_level level); @@ -217,62 +218,62 @@ namespace mtconnect { /// @param level the new logging level /// @return the logging level boost::log::trivial::severity_level setLoggingLevel(const std::string &level); - + std::optional findConfigFile(const std::string &file) { return findFile(m_configPaths, file); } - + std::optional findDataFile(const std::string &file) { return findFile(m_dataPaths, file); } - + /// @brief Create a sink contract with functions to find config and data files. /// @return shared pointer to a sink contract sink::SinkContractPtr makeSinkContract() { auto contract = m_agent->makeSinkContract(); contract->m_findConfigFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findConfigFile(n); }; contract->m_findDataFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findDataFile(n); }; return contract; } - + /// @brief add a path to the config paths /// @param path the path to add void addConfigPath(const std::filesystem::path &path) { addPathBack(m_configPaths, path); } - + /// @brief add a path to the data paths /// @param path the path to add void addDataPath(const std::filesystem::path &path) { addPathBack(m_dataPaths, path); } - + /// @brief add a path to the plugin paths /// @param path the path to add void addPluginPath(const std::filesystem::path &path) { addPathBack(m_pluginPaths, path); } - + protected: DevicePtr getDefaultDevice(); void loadAdapters(const ptree &tree, const ConfigOptions &options); void loadSinks(const ptree &sinks, ConfigOptions &options); - + #ifdef WITH_PYTHON void configurePython(const ptree &tree, ConfigOptions &options); #endif #ifdef WITH_RUBY void configureRuby(const ptree &tree, ConfigOptions &options); #endif - + void loadPlugins(const ptree &tree); bool loadPlugin(const std::string &name, const ptree &tree); void monitorFiles(boost::system::error_code ec); void scheduleMonitorTimer(); - + protected: std::optional findFile(const std::list &paths, const std::string file) @@ -284,33 +285,33 @@ namespace mtconnect { if (std::filesystem::exists(tst, ec) && !ec) { LOG(debug) << "Found file '" << file << "' " - << " in path " << path; + << " in path " << path; auto con {std::filesystem::canonical(tst)}; return con; } else { LOG(debug) << "Cannot find file '" << file << "' " - << " in path " << path; + << " in path " << path; } } - + return std::nullopt; } - + void addPathBack(std::list &paths, std::filesystem::path path) { std::error_code ec; auto con {std::filesystem::canonical(path, ec)}; if (std::find(paths.begin(), paths.end(), con) != paths.end()) return; - + if (!ec) paths.emplace_back(con); else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + void addPathFront(std::list &paths, std::filesystem::path path) { std::error_code ec; @@ -321,7 +322,7 @@ namespace mtconnect { else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + template void logPaths(T lvl, const std::list &paths) { @@ -329,52 +330,52 @@ namespace mtconnect { { BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(), (::boost::log::keywords::severity = lvl)) - << " " << p; + << " " << p; } } - + void expandConfigVariables(boost::property_tree::ptree &); - + protected: using text_sink = boost::log::sinks::synchronous_sink; using console_sink = - boost::log::sinks::synchronous_sink; - + boost::log::sinks::synchronous_sink; + struct LogChannel { std::string m_channelName; std::filesystem::path m_logDirectory; std::filesystem::path m_logArchivePattern; std::filesystem::path m_logFileName; - + int64_t m_maxLogFileSize {0}; int64_t m_logRotationSize {0}; int64_t m_rotationLogInterval {0}; - + boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; - + boost::shared_ptr m_logSink; }; - + std::map m_logChannels; - + std::map m_initializers; - + std::unique_ptr m_context; std::unique_ptr m_agent; - + pipeline::PipelineContextPtr m_pipelineContext; std::unique_ptr m_adapterHandler; - + std::string m_version; std::string m_devicesFile; std::filesystem::path m_exePath; std::filesystem::path m_working; - + std::list m_configPaths; std::list m_dataPaths; std::list m_pluginPaths; - + // File monitoring boost::asio::steady_timer m_monitorTimer; bool m_monitorFiles = false; @@ -383,23 +384,23 @@ namespace mtconnect { bool m_restart = false; std::optional m_configTime; std::optional m_deviceTime; - + // Factories sink::SinkFactory m_sinkFactory; source::SourceFactory m_sourceFactory; - + int m_workerThreadCount {1}; - + // Reference to the global logger boost::log::trivial::logger_type *m_logger {nullptr}; - + #ifdef WITH_RUBY std::unique_ptr m_ruby; #endif #ifdef WITH_PYTHON std::unique_ptr m_python; #endif - + HookManager m_afterAgentHooks; HookManager m_beforeStartHooks; HookManager m_beforeStopHooks; diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp index aa92c3e5b..c13b037cf 100644 --- a/src/mtconnect/configuration/async_context.hpp +++ b/src/mtconnect/configuration/async_context.hpp @@ -20,11 +20,12 @@ #include #include +#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" namespace mtconnect::configuration { - + /// @brief Manages the boost asio context and allows for a syncronous /// callback to execute when all the worker threads have stopped. class AGENT_LIB_API AsyncContext @@ -32,79 +33,131 @@ namespace mtconnect::configuration { public: using SyncCallback = std::function; using WorkGuard = boost::asio::executor_work_guard; - + /// @brief creates an asio context and a guard to prevent it from /// stopping AsyncContext() { m_guard.emplace(m_context.get_executor()); } /// @brief removes the copy constructor AsyncContext(const AsyncContext &) = delete; ~AsyncContext() {} - + /// @brief is the context running /// @returns running status auto isRunning() { return m_running; } - + /// @brief return the paused state /// @returns the paused state auto isPauased() { return m_paused; } - + /// @brief Testing only: method to remove the run guard from the context void removeGuard() { m_guard.reset(); } - + /// @brief get the boost asio context reference auto &get() { return m_context; } - + /// @brief operator() returns a reference to the io context /// @return the io context operator boost::asio::io_context &() { return m_context; } - + /// @brief sets the number of theads for asio thread pool /// @param[in] threads number of threads void setThreadCount(int threads) { m_threadCount = threads; } - + /// @brief start `m_threadCount` worker threads - void start() + /// @returns the exit code + int start() { - m_running = true; - m_paused = false; - do + m_exitCode = 0; + try { - for (int i = 0; i < m_threadCount; i++) + m_running = true; + m_paused = false; + do { - m_workers.emplace_back(boost::thread([this]() { m_context.run(); })); - } - auto &first = m_workers.front(); - while (m_running && !m_paused) - { - if (!first.try_join_for(boost::chrono::seconds(5)) && !m_running) + for (int i = 0; i < m_threadCount; i++) { - if (!first.try_join_for(boost::chrono::seconds(5))) - m_context.stop(); + m_workers.emplace_back(boost::thread([this]() + { + try + { + m_context.run(); + } + + catch (FatalException &e) + { + LOG(fatal) << "Fatal exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (std::exception &e) + { + LOG(fatal) << "Uncaught exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (...) + { + LOG(fatal) << "Unknown fatal exception occurred"; + stop(); + m_exitCode = 1; + } + + })); } - } - - for (auto &w : m_workers) - { - w.join(); - } - m_workers.clear(); - - if (m_delayedStop.joinable()) - m_delayedStop.join(); - - if (m_syncCallback) - { - m_syncCallback(*this); - if (m_running) + auto &first = m_workers.front(); + while (m_running && !m_paused) { - m_syncCallback = nullptr; - restart(); + if (!first.try_join_for(boost::chrono::seconds(5)) && !m_running) + { + if (!first.try_join_for(boost::chrono::seconds(5))) + m_context.stop(); + } } - } - - } while (m_running); + + for (auto &w : m_workers) + { + w.join(); + } + m_workers.clear(); + + if (m_delayedStop.joinable()) + m_delayedStop.join(); + + if (m_syncCallback && m_exitCode == 0) + { + m_syncCallback(*this); + if (m_running) + { + m_syncCallback = nullptr; + restart(); + } + } + + } while (m_running); + } + + catch (FatalException &e) + { + LOG(fatal) << "Fatal exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (std::exception &e) + { + LOG(fatal) << "Uncaught exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (...) + { + LOG(fatal) << "Unknown fatal exception occurred"; + stop(); + m_exitCode = 1; + } + + return m_exitCode; } - + /// @brief pause the worker threads. Sets a callback when the threads are paused. /// @param[in] callback the callback to call /// @param[in] safeStop stops by resetting the the guard, otherwise stop the @@ -118,7 +171,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief stop the worker threads /// @param safeStop if `true` resets the guard or stops the context void stop(bool safeStop = true) @@ -129,7 +182,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief restarts the worker threads when paused void restart() { @@ -138,58 +191,59 @@ namespace mtconnect::configuration { m_guard.emplace(m_context.get_executor()); m_context.restart(); } - + /// @name Cover methods for asio io_context /// @{ - + /// @brief io_context::run_for template auto run_for(const std::chrono::duration &rel_time) { return m_context.run_for(rel_time); } - + /// @brief io_context::run auto run() { return m_context.run(); } - + /// @brief io_context::run_one auto run_one() { return m_context.run_one(); } - + /// @brief io_context::run_one_for template auto run_one_for(const std::chrono::duration &rel_time) { return m_context.run_one_for(rel_time); } - + /// @brief io_context::run_one_until template auto run_one_until(const std::chrono::time_point &abs_time) { return m_context.run_one_for(abs_time); } - + /// @brief io_context::poll auto poll() { return m_context.poll(); } - + /// @brief io_context::poll auto get_executor() BOOST_ASIO_NOEXCEPT { return m_context.get_executor(); } - + /// @} - + private: void operator=(const AsyncContext &) {} - + protected: boost::asio::io_context m_context; std::list m_workers; SyncCallback m_syncCallback; std::optional m_guard; std::thread m_delayedStop; - + int m_threadCount = 1; bool m_running = false; bool m_paused = false; + int m_exitCode = 0; }; - + } // namespace mtconnect::configuration diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index a98185912..4c47c95ff 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -176,6 +176,8 @@ namespace mtconnect { using namespace std; + int res = 0; + try { // If command-line parameter is "install", install the service. If debug or run @@ -208,9 +210,9 @@ namespace mtconnect { m_isDebug = true; initialize(options); - start(); + res = start(); std::thread cmd(commandLine); - return 0; + return res; } else { @@ -236,14 +238,16 @@ namespace mtconnect { { LOG(fatal) << "Agent top level exception: " << e.what(); std::cerr << "Agent top level exception: " << e.what() << std::endl; + res = 1; } catch (std::string &s) { LOG(fatal) << "Agent top level exception: " << s; std::cerr << "Agent top level exception: " << s << std::endl; + res = 1; } - return 0; + return res; } bool MTConnectService::isElevated() @@ -841,8 +845,7 @@ namespace mtconnect { usage(1); } - start(); - return 0; + return start(); } void MTConnectService::install() {} diff --git a/src/mtconnect/configuration/service.hpp b/src/mtconnect/configuration/service.hpp index 7795965be..60e6f736d 100644 --- a/src/mtconnect/configuration/service.hpp +++ b/src/mtconnect/configuration/service.hpp @@ -44,7 +44,7 @@ namespace mtconnect { /// @brief stop the srvice virtual void stop() = 0; /// @brief start the service - virtual void start() = 0; + virtual int start() = 0; /// @brief set the name of the service /// @param[in] name name of the service diff --git a/src/mtconnect/device_model/agent_device.cpp b/src/mtconnect/device_model/agent_device.cpp index 287e4065a..d88257488 100644 --- a/src/mtconnect/device_model/agent_device.cpp +++ b/src/mtconnect/device_model/agent_device.cpp @@ -66,6 +66,7 @@ namespace mtconnect { { LOG(fatal) << "Cannot create AgentDevice: " << e->what(); } + throw FatalException(); } } diff --git a/src/mtconnect/device_model/data_item/data_item.cpp b/src/mtconnect/device_model/data_item/data_item.cpp index a6828900c..d6dfd230a 100644 --- a/src/mtconnect/device_model/data_item/data_item.cpp +++ b/src/mtconnect/device_model/data_item/data_item.cpp @@ -130,7 +130,7 @@ namespace mtconnect { auto &category = get("category"); auto units = maybeGet("units"); - if (units && ends_with(*units, "3D")) + if (units && units->ends_with("3D")) m_specialClass = THREE_SPACE_CLS; if (category == "SAMPLE") diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index fea72bc30..b5c2bbd1c 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -60,9 +60,11 @@ namespace mtconnect { { if (auto it = m_dataItems.get().find(di->getId()); it != m_dataItems.get().end()) { - LOG(fatal) << "Device " << getName() << ": Duplicatie data item id '" << di->getId() + stringstream msg; + msg << "Device " << getName() << ": Duplicatie data item id '" << di->getId() << "', Exiting"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } if (di->hasProperty("Source") && di->getSource()->hasValue()) @@ -93,9 +95,11 @@ namespace mtconnect { auto [id, added] = m_dataItems.emplace(di); if (!added) { - LOG(fatal) << "Device " << getName() << ": DataItem '" << di->getId() + stringstream msg; + msg << "Device " << getName() << ": DataItem '" << di->getId() << " could not be added, exiting"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } diff --git a/src/mtconnect/entity/factory.cpp b/src/mtconnect/entity/factory.cpp index eb93aebd5..3e1591760 100644 --- a/src/mtconnect/entity/factory.cpp +++ b/src/mtconnect/entity/factory.cpp @@ -188,7 +188,7 @@ namespace mtconnect { // If the property is a namespace declaration, then // remove it if it is related to mtconnect, otherwise // allow it to pass through. - if (ba::starts_with(p.first.str(), "xmlns"s)) + if (p.first.str().starts_with("xmlns"sv)) { if (holds_alternative(p.second) && ba::contains(get(p.second), "mtconnect"s)) diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index 58f276b16..e50f9d46c 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -66,40 +66,40 @@ namespace mtconnect { // regex(".+TimeSeries$") factory->registerFactory( - [](const std::string &name) { return ends_with(name, "TimeSeries"); }, + [](const std::string &name) { return name.ends_with("TimeSeries"); }, Timeseries::getFactory()); - factory->registerFactory([](const std::string &name) { return ends_with(name, "DataSet"); }, + factory->registerFactory([](const std::string &name) { return name.ends_with("DataSet"); }, DataSetEvent::getFactory()); - factory->registerFactory([](const std::string &name) { return ends_with(name, "Table"); }, + factory->registerFactory([](const std::string &name) { return name.ends_with("Table"); }, TableEvent::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Condition:"); }, + [](const std::string &name) { return name.starts_with("Condition:"); }, Condition::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Samples:") && ends_with(name, ":3D"); + return name.starts_with("Samples:") && name.ends_with(":3D"); }, ThreeSpaceSample::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":3D"); + return name.starts_with("Events:") && name.ends_with(":3D"); }, ThreeSpaceSample::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Samples:"); }, + [](const std::string &name) { return name.starts_with("Samples:"); }, Sample::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":DOUBLE"); + return name.starts_with("Events:") && name.ends_with(":DOUBLE"); }, DoubleEvent::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":INT"); + return name.starts_with("Events:") && name.ends_with(":INT"); }, IntEvent::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Events:"); }, + [](const std::string &name) { return name.starts_with("Events:"); }, Event::getFactory()); } return factory; diff --git a/src/mtconnect/parser/xml_parser.cpp b/src/mtconnect/parser/xml_parser.cpp index 9f5363e75..3a03709a4 100644 --- a/src/mtconnect/parser/xml_parser.cpp +++ b/src/mtconnect/parser/xml_parser.cpp @@ -227,7 +227,7 @@ namespace mtconnect::parser { xmlXPathFreeContext(xpathCtx); LOG(fatal) << "Cannot parse XML file: " << e; - throw; + throw FatalException(e); } catch (...) { @@ -237,7 +237,7 @@ namespace mtconnect::parser { if (xpathCtx) xmlXPathFreeContext(xpathCtx); - throw; + throw FatalException(); } return deviceList; @@ -272,7 +272,7 @@ namespace mtconnect::parser { catch (string e) { LOG(fatal) << "Cannot parse XML document: " << e; - throw; + throw FatalException(); } return device; @@ -312,7 +312,7 @@ namespace mtconnect::parser { catch (string e) { LOG(fatal) << "Cannot parse XML document: " << e; - throw; + throw FatalException(); } } diff --git a/src/mtconnect/ruby/embedded.cpp b/src/mtconnect/ruby/embedded.cpp index f461d086b..57365e03e 100644 --- a/src/mtconnect/ruby/embedded.cpp +++ b/src/mtconnect/ruby/embedded.cpp @@ -186,18 +186,18 @@ namespace mtconnect::ruby { if (mrb_false_p(res)) { LOG(fatal) << "Error loading file " << *modulePath << ": exiting agent"; - exit(1); + throw FatalException("Fatal error loading module"); } } catch (std::exception ex) { LOG(fatal) << "Failed to load module: " << *modulePath << ": " << ex.what(); - exit(1); + throw FatalException("Fatal error loading module"); } catch (...) { LOG(fatal) << "Failed to load module: " << *modulePath; - exit(1); + throw FatalException("Fatal error loading module"); } if (fp != nullptr) { diff --git a/src/mtconnect/sink/rest_sink/file_cache.cpp b/src/mtconnect/sink/rest_sink/file_cache.cpp index 4c1ae8f5c..ceec74ffc 100644 --- a/src/mtconnect/sink/rest_sink/file_cache.cpp +++ b/src/mtconnect/sink/rest_sink/file_cache.cpp @@ -263,7 +263,7 @@ namespace mtconnect::sink::rest_sink { for (const auto &dir : m_directories) { - if (boost::starts_with(name, dir.first)) + if (name.starts_with(dir.first)) { auto fileName = boost::erase_first_copy(name, dir.first); if (fileName.empty()) @@ -382,7 +382,7 @@ namespace mtconnect::sink::rest_sink { if (fs::exists(path)) { string root(uri); - if (boost::ends_with(root, "/")) + if (root.ends_with("/")) { boost::erase_last(root, "/"); } diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 46775087c..41108936f 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -473,7 +473,7 @@ namespace mtconnect { auto format = request->parameter("format"); auto printer = getPrinter(request->m_accepts, format); - if (device && !ends_with(request->m_path, string("probe")) && + if (device && !request->m_path.ends_with("probe"sv) && m_sinkContract->findDeviceByUUIDorName(*device) == nullptr) return false; @@ -1473,7 +1473,7 @@ namespace mtconnect { { for (const auto &p : m_sinkContract->getPrinters()) { - if (ends_with(accept, p.first)) + if (accept.ends_with(p.first)) return p.first; } } diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp index 550715817..b6b23707d 100644 --- a/src/mtconnect/sink/rest_sink/server.cpp +++ b/src/mtconnect/sink/rest_sink/server.cpp @@ -43,12 +43,12 @@ namespace mtconnect::sink::rest_sink { using tcp = boost::asio::ip::tcp; namespace algo = boost::algorithm; namespace ssl = boost::asio::ssl; - + using namespace std; using namespace rapidjson; using std::placeholders::_1; using std::placeholders::_2; - + void Server::loadTlsCertificate() { if (HasOption(m_options, configuration::TlsCertificateChain) && @@ -59,27 +59,27 @@ namespace mtconnect::sink::rest_sink { if (HasOption(m_options, configuration::TlsCertificatePassword)) { m_sslContext.set_password_callback( - [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { - return *GetOption(m_options, configuration::TlsCertificatePassword); - }); + [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { + return *GetOption(m_options, configuration::TlsCertificatePassword); + }); } - + m_sslContext.set_options(ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); m_sslContext.use_certificate_chain_file( - *GetOption(m_options, configuration::TlsCertificateChain)); + *GetOption(m_options, configuration::TlsCertificateChain)); m_sslContext.use_private_key_file(*GetOption(m_options, configuration::TlsPrivateKey), asio::ssl::context::file_format::pem); m_sslContext.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - + m_tlsEnabled = true; - + m_tlsOnly = IsOptionSet(m_options, configuration::TlsOnly); - + if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) { LOG(info) << "Will only accept client connections with valid certificates"; - + m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); if (HasOption(m_options, configuration::TlsClientCAs)) { @@ -89,7 +89,7 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::start() { try @@ -100,17 +100,17 @@ namespace mtconnect::sink::rest_sink { catch (exception &e) { LOG(fatal) << "Cannot start server: " << e.what(); - std::exit(1); + throw FatalException(e.what()); } } - + // Listen for an HTTP server connection void Server::listen() { NAMED_SCOPE("Server::listen"); - + beast::error_code ec; - + // Blocking call to listen for a connection tcp::endpoint ep(m_address, m_port); m_acceptor.open(ep.protocol(), ec); @@ -135,23 +135,23 @@ namespace mtconnect::sink::rest_sink { { m_port = m_acceptor.local_endpoint().port(); } - + m_acceptor.listen(net::socket_base::max_listen_connections, ec); if (ec) { fail(ec, "Cannot set listen queue length"); return; } - + m_listening = true; m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } - + bool Server::allowPutFrom(const std::string &host) { NAMED_SCOPE("Server::allowPutFrom"); - + // Resolve the host to an ip address to verify remote addr beast::error_code ec; ip::tcp::resolver resolve(m_context); @@ -162,25 +162,25 @@ namespace mtconnect::sink::rest_sink { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); return false; } - + // Add the results to the set of allowed hosts for (auto &addr : results) { m_allowPutsFrom.insert(addr.endpoint().address()); } m_allowPuts = true; - + return true; } - + void Server::accept(beast::error_code ec, tcp::socket socket) { NAMED_SCOPE("Server::accept"); - + if (ec) { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); - + fail(ec, "Accept failed"); } else if (m_run) @@ -188,7 +188,7 @@ namespace mtconnect::sink::rest_sink { auto dispatcher = [this](SessionPtr session, RequestPtr request) { if (!m_run) return false; - + if (m_lastSession) m_lastSession(session); dispatch(session, request); @@ -197,9 +197,9 @@ namespace mtconnect::sink::rest_sink { if (m_tlsEnabled) { auto dectector = - make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, - m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); - + make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, + m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); + dectector->run(); } else @@ -208,34 +208,34 @@ namespace mtconnect::sink::rest_sink { boost::beast::tcp_stream stream(std::move(socket)); auto session = make_shared(std::move(stream), std::move(buffer), m_fields, dispatcher, m_errorFunction); - + if (!m_allowPutsFrom.empty()) session->allowPutsFrom(m_allowPutsFrom); else if (m_allowPuts) session->allowPuts(); - + session->run(); } m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } } - + //------------------------------------------------------------------------------ - + // Report a failure void Server::fail(beast::error_code ec, char const *what) { LOG(error) << " error: " << ec.message(); } - + using namespace mtconnect::printer; - + template void AddParameter(T &writer, const Parameter ¶m) { AutoJsonObject obj(writer); - + obj.AddPairs("name", param.m_name, "in", param.m_part == PATH ? "path" : "query", "required", param.m_part == PATH); { @@ -245,23 +245,23 @@ namespace mtconnect::sink::rest_sink { case ParameterType::STRING: obj.AddPairs("type", "string", "format", "string"); break; - + case ParameterType::INTEGER: obj.AddPairs("type", "integer", "format", "int64"); break; - + case ParameterType::UNSIGNED_INTEGER: obj.AddPairs("type", "integer", "format", "uint64"); break; - + case ParameterType::DOUBLE: obj.AddPairs("type", "double", "format", "double"); break; - + case ParameterType::BOOL: obj.AddPairs("type", "boolean", "format", "bool"); break; - + case ParameterType::NONE: obj.AddPairs("type", "unknown", "format", "unknown"); break; @@ -270,30 +270,30 @@ namespace mtconnect::sink::rest_sink { { obj.Key("default"); visit( - overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, - [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, - [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, - param.m_default); + overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, + [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, + [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, + param.m_default); } } if (param.m_description) obj.AddPairs("description", *param.m_description); } - + template void AddRouting(T &writer, const Routing &routing) { string verb {to_string(routing.getVerb())}; boost::to_lower(verb); - + { AutoJsonObject obj(writer, verb.data()); - + if (routing.getSummary()) obj.AddPairs("summary", *routing.getSummary()); if (routing.getDescription()) obj.AddPairs("description", *routing.getDescription()); - + if (!routing.getPathParameters().empty() || !routing.getQueryParameters().empty()) { AutoJsonArray ary(writer, "parameters"); @@ -306,7 +306,7 @@ namespace mtconnect::sink::rest_sink { AddParameter(writer, param); } } - + { AutoJsonObject obj(writer, "responses"); { @@ -326,36 +326,36 @@ namespace mtconnect::sink::rest_sink { } } } - + // Swagger stuff template const void Server::renderSwaggerResponse(T &writer) { { AutoJsonObject obj(writer); - + obj.AddPairs("openapi", "3.0.0"); - + { AutoJsonObject obj(writer, "info"); obj.AddPairs("title", "MTConnect – REST API", "description", "MTConnect REST API "); - + { AutoJsonObject obj(writer, "contact"); obj.AddPairs("email", "will@metalogi.io"); } { AutoJsonObject obj(writer, "license"); - + obj.AddPairs("name", "Apache 2.0", "url", "http://www.apache.org/licenses/LICENSE-2.0.html"); } - + obj.AddPairs("version", GetAgentVersion()); } { AutoJsonObject obj(writer, "externalDocs"); - + obj.AddPairs("description", "For information related to MTConnect", "url", "http://mtconnect.org"); } @@ -363,27 +363,27 @@ namespace mtconnect::sink::rest_sink { AutoJsonArray ary(writer, "servers"); { AutoJsonObject obj(writer); - + stringstream str; if (m_tlsEnabled) str << "https://"; else str << "http://"; - + str << GetBestHostAddress(m_context, true) << ':' << m_port << '/'; obj.AddPairs("url", str.str()); } } { AutoJsonObject obj(writer, "paths"); - + multimap routings; for (const auto &routing : m_routings) { if (!routing.isSwagger() && routing.getPath()) routings.emplace(make_pair(*routing.getPath(), &routing)); } - + AutoJsonObject robj(writer, false); for (const auto &[path, routing] : routings) { @@ -394,23 +394,23 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::addSwaggerRoutings() { auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { auto pretty = *request->parameter("pretty"); - + StringBuffer output; RenderJson(output, pretty, [this](auto &writer) { renderSwaggerResponse(writer); }); - + session->writeResponse( - make_unique(status::ok, string(output.GetString()), "application/json")); - + make_unique(status::ok, string(output.GetString()), "application/json")); + return true; }; - + addRouting({boost::beast::http::verb::get, "/swagger?pretty={bool:false}", handler, true}); // addRouting({boost::beast::http::verb::get, "/swagger.yaml", handler, true}); } - + } // namespace mtconnect::sink::rest_sink diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index b99bff088..4beda3b69 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -125,7 +125,10 @@ namespace mtconnect::source::adapter::agent_adapter { } else if (device || HasOption(m_options, configuration::SourceDevice)) { - m_sourceDevice = GetOption(m_options, configuration::SourceDevice).value_or(*device); + + m_sourceDevice = GetOption(m_options, configuration::SourceDevice); + if (!m_sourceDevice) + m_sourceDevice = device; } else { diff --git a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp index 1557d46d3..02765498a 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp @@ -25,6 +25,7 @@ #include #include +#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/pipeline/mtconnect_xml_transform.hpp" #include "mtconnect/pipeline/response_document.hpp" @@ -37,7 +38,7 @@ namespace mtconnect::source::adapter::agent_adapter { namespace http = boost::beast::http; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; - + /// @brief A session implementation that where the derived classes can support HTTP or HTTPS /// @tparam Derived template @@ -45,7 +46,7 @@ namespace mtconnect::source::adapter::agent_adapter { { /// @brief A list of HTTP requests using RequestQueue = std::list; - + public: /// @brief Cast this class as the derived class /// @return reference to the derived class @@ -53,22 +54,22 @@ namespace mtconnect::source::adapter::agent_adapter { /// @brief Immutably cast this class as its derived subclass /// @return const reference to the derived class const Derived &derived() const { return static_cast(*this); } - + // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. SessionImpl(boost::asio::io_context::strand &strand, const url::Url &url) - : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) + : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) {} - + virtual ~SessionImpl() { stop(); } - + /// @brief see if the socket is connected /// @return `true` if the socket is open bool isOpen() const override { return derived().lowestLayer().socket().is_open(); } - + /// @brief close the connection void close() override { derived().lowestLayer().socket().close(); } - + /// @brief Method called when a request fails /// /// Closes the socket and resets the request @@ -77,7 +78,7 @@ namespace mtconnect::source::adapter::agent_adapter { void failed(std::error_code ec, const char *what) override { derived().lowestLayer().socket().close(); - + LOG(error) << "Agent Adapter Connection Failed: " << m_url.getUrlText(nullopt); if (m_request) LOG(error) << "Agent Adapter Target: " << m_request->getTarget(m_url); @@ -86,15 +87,15 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_failed) m_failed(ec); } - + void stop() override { m_request.reset(); } - + bool makeRequest(const Request &req) override { if (!m_request) { m_request.emplace(req); - + // Clean out any previous data m_buffer.clear(); m_contentType.clear(); @@ -106,7 +107,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_hasHeader = false; if (m_chunk.size() > 0) m_chunk.consume(m_chunk.size()); - + // Check if we are discussected. if (!derived().lowestLayer().socket().is_open()) { @@ -126,7 +127,7 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } } - + /// @brief Process data from the remote agent /// @param data the payload from the agent void processData(const std::string &data) @@ -136,6 +137,10 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_handler && m_handler->m_processData) m_handler->m_processData(data, m_identity); } + catch (FatalException &e) + { + throw e; + } catch (std::system_error &e) { LOG(warning) << "AgentAdapter - Error occurred processing data: " << e.what(); @@ -157,7 +162,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Unknown exception in AgentAdapter::processData"); } } - + /// @brief Connect to the remote agent virtual void connect() { @@ -170,26 +175,26 @@ namespace mtconnect::source::adapter::agent_adapter { else if (holds_alternative(m_url.m_host)) { asio::ip::tcp::endpoint ep(get(m_url.m_host), m_url.getPort()); - + // Create the results type and call on resolve directly. using results_type = tcp::resolver::results_type; auto results = results_type::create(ep, m_url.getHost(), m_url.getService()); - + beast::error_code ec; onResolve(ec, results); } else { derived().lowestLayer().expires_after(m_timeout); - + // Do an async resolution of the address. m_resolver.async_resolve( - get(m_url.m_host), m_url.getService(), - asio::bind_executor( - m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); + get(m_url.m_host), m_url.getService(), + asio::bind_executor( + m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); } } - + /// @brief Callback when the host name needs to be resolved /// @param ec error code if resultion fails /// @param results the resolution results @@ -202,34 +207,34 @@ namespace mtconnect::source::adapter::agent_adapter { LOG(error) << " Reason: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(source::ErrorCode::ADAPTER_FAILED), "resolve"); } - + if (!m_request) { LOG(error) << "Resolved but no request"; return; } - + if (!m_resolution) m_resolution.emplace(results); - + if (m_handler && m_handler->m_connecting) m_handler->m_connecting(m_identity); - + // Set a timeout on the operation derived().lowestLayer().expires_after(m_timeout); - + // Make the connection on the IP address we get from a lookup derived().lowestLayer().async_connect( - results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, - derived().getptr()))); + results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, + derived().getptr()))); } - + /// @brief Write a request to the remote agent void request() { if (!m_request) return; - + // Set up an HTTP GET request message m_req.emplace(); m_req->version(11); @@ -238,50 +243,50 @@ namespace mtconnect::source::adapter::agent_adapter { m_req->set(http::field::host, m_url.getHost()); m_req->set(http::field::user_agent, "MTConnect Agent/2.0"); m_req->set(http::field::connection, "keep-alive"); - + if (m_closeConnectionAfterResponse) { m_req->set(http::field::connection, "close"); } - + derived().lowestLayer().expires_after(m_timeout); - + LOG(debug) << "Agent adapter making request: " << m_url.getUrlText(nullopt) << " target " - << m_request->getTarget(m_url); - + << m_request->getTarget(m_url); + http::async_write(derived().stream(), *m_req, beast::bind_front_handler(&SessionImpl::onWrite, derived().getptr())); } - + /// @brief Callback on successful write to the agent /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onWrite(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Cannot send request: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + if (!m_request) { LOG(error) << "Wrote but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + derived().lowestLayer().expires_after(m_timeout); - + // Receive the HTTP response m_headerParser.emplace(); http::async_read_header( - derived().stream(), m_buffer, *m_headerParser, - asio::bind_executor(m_strand, - beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); + derived().stream(), m_buffer, *m_headerParser, + asio::bind_executor(m_strand, + beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); } - + /// @brief Callback after write to process the message header /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred @@ -290,7 +295,7 @@ namespace mtconnect::source::adapter::agent_adapter { if (ec) { LOG(error) << "Agent Adapter Error getting request header: " << ec.category().name() << " " - << ec.message(); + << ec.message(); derived().lowestLayer().close(); if (m_request->m_stream && ec == beast::error::timeout) { @@ -301,16 +306,16 @@ namespace mtconnect::source::adapter::agent_adapter { { return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + return; } - + if (!m_request) { LOG(error) << "Received a header but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + auto &msg = m_headerParser->get(); if (msg.version() < 11) { @@ -321,7 +326,7 @@ namespace mtconnect::source::adapter::agent_adapter { { m_closeOnRead = a->value() == "close"; } - + if (m_request->m_stream && m_headerParser->chunked()) { onChunkedContent(); @@ -329,49 +334,49 @@ namespace mtconnect::source::adapter::agent_adapter { else { derived().lowestLayer().expires_after(m_timeout); - + m_textParser.emplace(std::move(*m_headerParser)); http::async_read(derived().stream(), m_buffer, *m_textParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } } - + /// @brief Callback after header processing to read the body of the response /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onRead(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Error getting response: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "read"); } - + if (!m_request) { LOG(error) << "read data but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + derived().lowestLayer().expires_after(m_timeout); - + if (!derived().lowestLayer().socket().is_open()) derived().disconnect(); - + processData(m_textParser->get().body()); - + m_textParser.reset(); m_req.reset(); - + auto next = m_request->m_next; m_request.reset(); - + if (m_closeOnRead) close(); - + if (next) { next(); @@ -383,10 +388,10 @@ namespace mtconnect::source::adapter::agent_adapter { makeRequest(req); } } - + /// @name Streaming related methods ///@{ - + /// @brief Find the x-multipart-replace MIME boundary /// @return the boundary string inline string findBoundary() @@ -410,10 +415,10 @@ namespace mtconnect::source::adapter::agent_adapter { } } } - + return ""; } - + /// @brief Create a function to handle the chunk header /// /// Sets the chunk parser on chunk header @@ -428,32 +433,32 @@ namespace mtconnect::source::adapter::agent_adapter { cout << "Ext: " << c.first << ": " << c.second << endl; #endif derived().lowestLayer().expires_after(m_timeout); - + if (ec) { return failed(ec, "Failed in chunked extension parse"); } }; - + m_chunkParser->on_chunk_header(m_chunkHeaderHandler); } - + /// @brief Parse the header and get the size and type /// @return `true` if successful bool parseMimeHeader() { using namespace boost; namespace algo = boost::algorithm; - + if (m_chunk.data().size() < 128) { LOG(trace) << "Not enough data for mime header: " << m_chunk.data().size(); return false; } - + auto start = static_cast(m_chunk.data().data()); boost::string_view view(start, m_chunk.data().size()); - + auto bp = view.find(m_boundary.c_str()); if (bp == boost::string_view::npos) { @@ -463,7 +468,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + auto ep = view.find("\r\n\r\n", bp); if (bp == boost::string_view::npos) { @@ -474,11 +479,11 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } ep += 4; - + using string_view_range = boost::iterator_range; auto svi = string_view_range(view.begin() + bp, view.end()); auto lp = boost::ifind_first(svi, boost::string_view("content-length:")); - + if (lp.empty()) { LOG(warning) << "Cannot find the content-length"; @@ -487,7 +492,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + boost::string_view length(lp.end()); auto digits = length.substr(0, length.find("\n")); auto finder = boost::token_finder(algo::is_digit(), algo::token_compress_on); @@ -500,14 +505,14 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + m_chunkLength = boost::lexical_cast(rng); m_hasHeader = true; m_chunk.consume(ep); - + return true; } - + /// @brief Creates the function to handle chunk body /// /// Sets the chunk parse on chunk body. @@ -522,15 +527,15 @@ namespace mtconnect::source::adapter::agent_adapter { "Stream body but no request"); return body.size(); } - + { std::ostream cstr(&m_chunk); cstr << body; } - + LOG(trace) << "Received: -------- " << m_chunk.size() << " " << remain << "\n" - << body << "\n-------------"; - + << body << "\n-------------"; + if (!m_hasHeader) { if (!parseMimeHeader()) @@ -539,27 +544,27 @@ namespace mtconnect::source::adapter::agent_adapter { return body.size(); } } - + auto len = m_chunk.size(); if (len >= m_chunkLength) { auto start = static_cast(m_chunk.data().data()); string_view sbuf(start, m_chunkLength); - + LOG(trace) << "Received Chunk: --------\n" << sbuf << "\n-------------"; - + processData(string(sbuf)); - + m_chunk.consume(m_chunkLength); m_hasHeader = false; } - + return body.size(); }; - + m_chunkParser->on_chunk_body(m_chunkHandler); } - + /// @brief Begins the async chunk reading if the boundry is found void onChunkedContent() { @@ -570,48 +575,48 @@ namespace mtconnect::source::adapter::agent_adapter { failed(ec, "Cannot find boundary"); return; } - + LOG(trace) << "Found boundary: " << m_boundary; - + m_chunkParser.emplace(std::move(*m_headerParser)); createChunkHeaderHandler(); createChunkBodyHandler(); - + derived().lowestLayer().expires_after(m_timeout); - + http::async_read(derived().stream(), m_buffer, *m_chunkParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } - + protected: tcp::resolver m_resolver; std::optional m_resolution; beast::flat_buffer m_buffer; // (Must persist between reads) std::optional> m_req; - + // Chunked content handling. std::optional> m_headerParser; std::optional> m_chunkParser; std::optional> m_textParser; asio::io_context::strand m_strand; url::Url m_url; - + std::function - m_chunkHandler; + m_chunkHandler; std::function - m_chunkHeaderHandler; - + m_chunkHeaderHandler; + std::string m_boundary; std::string m_contentType; - + size_t m_chunkLength; bool m_hasHeader = false; boost::asio::streambuf m_chunk; - + // For request queuing std::optional m_request; RequestQueue m_queue; }; - + } // namespace mtconnect::source::adapter::agent_adapter diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index 82533ae81..964b0de52 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -195,9 +195,11 @@ namespace mtconnect { } else if (!HasOption(options, configuration::Topics)) { - LOG(error) << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " + stringstream msg; + msg << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " "' or Topics block"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } /// diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 70bddd58f..6187923b7 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -66,7 +66,42 @@ namespace boost::asio { namespace mtconnect { // Message for when enumerations do not exist in an array/enumeration const int ENUM_MISS = -1; - + + /// @brtief Fatal Error Exception + /// An exception that get thrown to shut down the application. Only caught by the top level worker thread. + class FatalException : public std::exception + { + public: + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const std::string &str) : m_what(str) {} + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const std::string_view &str) : m_what(str) {} + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const char *str) : m_what(str) {} + /// @brief Create a default fatal exception + /// Has the message `Fatal Exception Occurred` + FatalException() : m_what("Fatal Exception Occurred") {} + /// @brief Copy construction from an exception + /// @param ex the exception + FatalException(const std::exception &ex) : m_what(ex.what()) {} + /// @brief Defaut construction + FatalException(const FatalException &) = default; + /// @brief Default destructor + ~FatalException() = default; + + /// @brief gets the message + /// @returns the message as a string + const char* what() const noexcept override { return m_what.c_str(); } + + protected: + std::string m_what; + + }; + + /// @brief Time formats enum TimeFormat { @@ -75,7 +110,7 @@ namespace mtconnect { GMT_UV_SEC, ///< GMT with microsecond resolution LOCAL ///< Time using local time zone }; - + /// @brief Converts string to floating point numberss /// @param[in] text the number /// @return the converted value or 0.0 if incorrect. @@ -96,7 +131,7 @@ namespace mtconnect { } return value; } - + /// @brief Converts string to integer /// @param[in] text the number /// @return the converted value or 0 if incorrect. @@ -117,7 +152,7 @@ namespace mtconnect { } return value; } - + /// @brief converts a double to a string /// @param[in] value the double /// @return the string representation of the double (10 places max) @@ -128,18 +163,18 @@ namespace mtconnect { s << std::setprecision(precision) << value; return s.str(); } - + /// @brief inline formattor support for doubles class format_double_stream { protected: double val; - + public: /// @brief create a formatter /// @param[in] v the value format_double_stream(double v) { val = v; } - + /// @brief writes a double to an output stream with up to 10 digits of precision /// @tparam _CharT from std::basic_ostream /// @tparam _Traits from std::basic_ostream @@ -148,19 +183,19 @@ namespace mtconnect { /// @return reference to the output stream template inline friend std::basic_ostream<_CharT, _Traits> &operator<<( - std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) + std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) { constexpr int precision = std::numeric_limits::digits10; os << std::setprecision(precision) << fmter.val; return os; } }; - + /// @brief create a `format_doulble_stream` /// @param[in] v the value /// @return the format_double_stream inline format_double_stream formatted(double v) { return format_double_stream(v); } - + /// @brief Convert text to upper case /// @param[in,out] text text /// @return upper-case of text as string @@ -168,10 +203,10 @@ namespace mtconnect { { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::toupper(c); }); - + return text; } - + /// @brief Simple check if a number as a string is negative /// @param s the numbeer /// @return `true` if positive @@ -182,10 +217,10 @@ namespace mtconnect { if (!isdigit(c)) return false; } - + return true; } - + /// @brief Checks if a string is a valid integer /// @param s the string /// @return `true` if is `[+-]\d+` @@ -194,21 +229,21 @@ namespace mtconnect { auto iter = s.cbegin(); if (*iter == '-' || *iter == '+') ++iter; - + for (; iter != s.end(); iter++) { if (!isdigit(*iter)) return false; } - + return true; } - + /// @brief Gets the local time /// @param[in] time the time /// @param[out] buf struct tm AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -240,10 +275,10 @@ namespace mtconnect { return date::format("%Y-%m-%dT%H:%M:%S%z", zt); } } - + return ""; } - + /// @brief get the current time in the given format /// /// cover method for `getCurrentTime()` with `system_clock::now()` @@ -254,7 +289,7 @@ namespace mtconnect { { return getCurrentTime(std::chrono::system_clock::now(), format); } - + /// @brief Get the current time as a unsigned uns64 since epoch /// @tparam timePeriod the resolution type of time /// @return the time as an uns64 @@ -262,18 +297,18 @@ namespace mtconnect { inline uint64_t getCurrentTimeIn() { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } - + /// @brief Current time in microseconds since epoch /// @return the time as uns64 in microsecnods inline uint64_t getCurrentTimeInMicros() { return getCurrentTimeIn(); } - + /// @brief Current time in seconds since epoch /// @return the time as uns64 in seconds inline uint64_t getCurrentTimeInSec() { return getCurrentTimeIn(); } - + /// @brief Parse the given time /// @param aTime the time in text /// @return uns64 in microseconds since epoch @@ -293,12 +328,12 @@ namespace mtconnect { date::from_stream(str, "%FT%T%Z", fields, &abbrev, &offset); if (!fields.ymd.ok() || !fields.tod.in_conventional_range()) return 0; - + micros microdays {date::sys_days(fields.ymd)}; auto us = fields.tod.to_duration().count() + microdays.time_since_epoch().count(); return us; } - + /// @brief escaped reserved XML characters from text /// @param data text with reserved characters escaped inline void replaceIllegalCharacters(std::string &data) @@ -306,41 +341,30 @@ namespace mtconnect { for (auto i = 0u; i < data.length(); i++) { char c = data[i]; - + switch (c) { case '&': data.replace(i, 1, "&"); break; - + case '<': data.replace(i, 1, "<"); break; - + case '>': data.replace(i, 1, ">"); break; } } } - + /// @brief add namespace prefixes to each element of the XPath /// @param[in] aPath the path to modify /// @param[in] aPrefix the prefix to add /// @return the modified path prefixed AGENT_LIB_API std::string addNamespace(const std::string aPath, const std::string aPrefix); - - /// @brief determines of a string ends with an ending - /// @param[in] value the string to check - /// @param[in] ending the ending to verify - /// @return `true` if the string ends with ending - inline bool ends_with(const std::string &value, const std::string_view &ending) - { - if (ending.size() > value.size()) - return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - } - + /// @brief removes white space at the beginning of a string /// @param[in,out] s the string /// @return string with spaces removed @@ -349,7 +373,7 @@ namespace mtconnect { boost::algorithm::trim_left(s); return s; } - + /// @brief removes whitespace from the end of the string /// @param[in,out] s the string /// @return string with spaces removed @@ -358,7 +382,7 @@ namespace mtconnect { boost::algorithm::trim_right(s); return s; } - + /// @brief removes spaces from the beginning and end of a string /// @param[in] s the string /// @return string with spaces removed @@ -367,7 +391,7 @@ namespace mtconnect { boost::algorithm::trim(s); return s; } - + /// @brief split a string into two parts using a ':' separator /// @param key the key to split /// @return a pair of the key and an optional prefix. @@ -379,18 +403,7 @@ namespace mtconnect { else return {key, std::nullopt}; } - - /// @brief determines of a string starts with a beginning - /// @param[in] value the string to check - /// @param[in] beginning the beginning to verify - /// @return `true` if the string begins with beginning - inline bool starts_with(const std::string &value, const std::string_view &beginning) - { - if (beginning.size() > value.size()) - return false; - return std::equal(beginning.begin(), beginning.end(), value.begin()); - } - + /// @brief Case insensitive equals /// @param a first string /// @param b second string @@ -399,14 +412,14 @@ namespace mtconnect { { if (a.size() != b.size()) return false; - + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); + return tolower(a) == tolower(b); + }); } - + using Attributes = std::map; - + /// @brief overloaded pattern for variant visitors using list of lambdas /// @tparam ...Ts list of lambda classes template @@ -416,7 +429,7 @@ namespace mtconnect { }; template overloaded(Ts...) -> overloaded; - + /// @brief Reverse an iterable /// @tparam T The iterable type template @@ -424,13 +437,13 @@ namespace mtconnect { { private: T &m_iterable; - + public: explicit reverse(T &iterable) : m_iterable(iterable) {} auto begin() const { return std::rbegin(m_iterable); } auto end() const { return std::rend(m_iterable); } }; - + /// @brief observation sequence type using SequenceNumber_t = uint64_t; /// @brief set of data item ids for filtering @@ -442,16 +455,16 @@ namespace mtconnect { using Timestamp = std::chrono::time_point; using Timestamp = std::chrono::time_point; using StringList = std::list; - + /// @name Configuration related methods ///@{ - + /// @brief Variant for configuration options using ConfigOption = std::variant; + Milliseconds, StringList>; /// @brief A map of name to option value using ConfigOptions = std::map; - + /// @brief Get an option if available /// @tparam T the option type /// @param options the set of options @@ -466,7 +479,7 @@ namespace mtconnect { else return std::nullopt; } - + /// @brief checks if a boolean option is set /// @param options the set of options /// @param name the name of the option @@ -479,7 +492,7 @@ namespace mtconnect { else return false; } - + /// @brief checks if there is an option /// @param[in] options the set of options /// @param[in] name the name of the option @@ -489,7 +502,7 @@ namespace mtconnect { auto v = options.find(name); return v != options.end(); } - + /// @brief convert an option from a string to a typed option /// @param[in] s the /// @param[in] def template for the option @@ -502,29 +515,29 @@ namespace mtconnect { { std::string sv = std::get(option); visit(overloaded {[&option, &sv](const std::string &) { - if (sv.empty()) - option = std::monostate(); - else - option = sv; - }, - [&option, &sv](const int &) { option = stoi(sv); }, - [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, - [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, - [&option, &sv](const double &) { option = stod(sv); }, - [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, - [&option, &sv](const StringList &) { - StringList list; - boost::split(list, sv, boost::is_any_of(",")); - for (auto &s : list) - boost::trim(s); - option = list; - }, - [](const auto &) {}}, + if (sv.empty()) + option = std::monostate(); + else + option = sv; + }, + [&option, &sv](const int &) { option = stoi(sv); }, + [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, + [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, + [&option, &sv](const double &) { option = stod(sv); }, + [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, + [&option, &sv](const StringList &) { + StringList list; + boost::split(list, sv, boost::is_any_of(",")); + for (auto &s : list) + boost::trim(s); + option = list; + }, + [](const auto &) {}}, def); } return option; } - + /// @brief convert from a string option to a size /// /// Recognizes the following suffixes: @@ -542,7 +555,7 @@ namespace mtconnect { using namespace std; using boost::regex; using boost::smatch; - + auto value = GetOption(options, name); if (value) { @@ -559,11 +572,11 @@ namespace mtconnect { case 'G': case 'g': size *= 1024; - + case 'M': case 'm': size *= 1024; - + case 'K': case 'k': size *= 1024; @@ -577,10 +590,10 @@ namespace mtconnect { throw std::runtime_error(msg.str()); } } - + return size; } - + /// @brief adds a property tree node to an option set /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the options set @@ -606,7 +619,7 @@ namespace mtconnect { } } } - + /// @brief adds a property tree node to an option set with defaults /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the option set @@ -634,7 +647,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief combine two option sets /// @param[in,out] options existing set of options /// @param[in] entries options to add or update @@ -645,7 +658,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief get options from a property tree and create typed options /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options option set to modify @@ -663,9 +676,9 @@ namespace mtconnect { } AddOptions(tree, options, entries); } - + /// @} - + /// @brief Format a timestamp as a string in microseconds /// @param[in] ts the timestamp /// @return the time with microsecond resolution @@ -683,7 +696,7 @@ namespace mtconnect { time.append("Z"); return time; } - + /// @brief Capitalize a word /// /// Has special treatment of acronyms like AC, DC, PH, etc. @@ -694,12 +707,12 @@ namespace mtconnect { std::string::const_iterator end) { using namespace std; - + // Exceptions to the rule const static std::unordered_map exceptions = { - {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, - {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; - + {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, + {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; + std::string_view s(&*start, distance(start, end)); const auto &w = exceptions.find(s); ostream_iterator out(camel); @@ -713,7 +726,7 @@ namespace mtconnect { transform(start + 1, end, out, ::tolower); } } - + /// @brief creates an upper-camel-case string from words separated by an underscore (`_`) with /// optional prefix /// @@ -727,20 +740,20 @@ namespace mtconnect { using namespace std; if (type.empty()) return ""; - + ostringstream camel; - + auto start = type.begin(); decltype(start) end; - + auto colon = type.find(':'); - + if (colon != string::npos) { prefix = type.substr(0ul, colon); start += colon + 1; } - + bool done; do { @@ -753,10 +766,10 @@ namespace mtconnect { start = end + 1; } } while (!done); - + return camel.str(); } - + /// @brief parse a string timestamp to a `Timestamp` /// @param timestamp[in] the timestamp as a string /// @return converted `Timestamp` @@ -771,22 +784,22 @@ namespace mtconnect { } return ts; } - -/// @brief Creates a comparable schema version from a major and minor number + + /// @brief Creates a comparable schema version from a major and minor number #define SCHEMA_VERSION(major, minor) (major * 100 + minor) - + /// @brief Get the default schema version of the agent as a string /// @return the version inline std::string StrDefaultSchemaVersion() { return std::to_string(AGENT_VERSION_MAJOR) + "." + std::to_string(AGENT_VERSION_MINOR); } - + inline constexpr int32_t IntDefaultSchemaVersion() { return SCHEMA_VERSION(AGENT_VERSION_MAJOR, AGENT_VERSION_MINOR); } - + /// @brief convert a string version to a major and minor as two integers separated by a char. /// @param s the version inline int32_t IntSchemaVersion(const std::string &s) @@ -804,12 +817,12 @@ namespace mtconnect { return SCHEMA_VERSION(major, minor); } } - + /// @brief Retrieve the best Host IP address from the network interfaces. /// @param[in] context the boost asio io_context for resolving the address /// @param[in] onlyV4 only consider IPV4 addresses if `true` std::string GetBestHostAddress(boost::asio::io_context &context, bool onlyV4 = false); - + /// @brief Function to create a unique id given a sha1 namespace and an id. /// /// Creates a base 64 encoded version of the string and removes any illegal characters @@ -823,27 +836,27 @@ namespace mtconnect { { using namespace std; using namespace boost::uuids::detail; - + sha1 sha(contextSha); - + constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"); constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; }; constexpr auto isIDChar = [isIDStartChar](unsigned char c) -> bool { return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-'; }; - + sha.process_bytes(id.data(), id.length()); sha1::digest_type digest; sha.get_digest(digest); - - auto data = (unsigned int *) digest; + + auto data = (unsigned int *) digest; string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); - + s.erase(len - 1); s.erase(std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar)), s.end()); - + // Check if the character is legal. if (!isIDStartChar(s[0])) { @@ -852,39 +865,39 @@ namespace mtconnect { s.erase(0, 1); s[0] = startc[c % startc.size()]; } - + s.erase(16); - + return s; } - + namespace url { using UrlQueryPair = std::pair; - + /// @brief A map of URL query parameters that can format as a string struct AGENT_LIB_API UrlQuery : public std::map { using std::map::map; - + /// @brief join the parameters as `=&=&...` /// @return std::string join() const { std::stringstream ss; bool has_pre = false; - + for (const auto &kv : *this) { if (has_pre) ss << '&'; - + ss << kv.first << '=' << kv.second; has_pre = true; } - + return ss.str(); } - + /// @brief Merge twos sets over-writing existing pairs set with `query` and adding new pairs /// @param query query to merge void merge(UrlQuery query) @@ -895,15 +908,15 @@ namespace mtconnect { } } }; - + /// @brief URL struct to parse and format URLs struct AGENT_LIB_API Url { /// @brief Variant for the Host that is either a host name or an ip address using Host = std::variant; - + std::string m_protocol; ///< either `http` or `https` - + Host m_host; ///< the host component std::optional m_username; ///< optional username std::optional m_password; ///< optional password @@ -911,22 +924,22 @@ namespace mtconnect { std::string m_path = "/"; ///< The path component UrlQuery m_query; ///< Query parameters std::string m_fragment; ///< The component after a `#` - + /// @brief Visitor to format the Host as a string struct HostVisitor { std::string operator()(std::string v) const { return v; } - + std::string operator()(boost::asio::ip::address v) const { return v.to_string(); } }; - + /// @brief Get the host as a string /// @return the host std::string getHost() const { return std::visit(HostVisitor(), m_host); } /// @brief Get the port as a string /// @return the port std::string getService() const { return boost::lexical_cast(getPort()); } - + /// @brief Get the path and the query portion of the URL /// @return the path and query std::string getTarget() const @@ -936,7 +949,7 @@ namespace mtconnect { else return m_path; } - + /// @brief Format a target using the existing host and port to make a request /// @param device an optional device /// @param operation the operation (probe,sample,current, or asset) @@ -948,7 +961,7 @@ namespace mtconnect { UrlQuery uq {m_query}; if (!query.empty()) uq.merge(query); - + std::stringstream path; path << m_path; if (m_path[m_path.size() - 1] != '/') @@ -959,10 +972,10 @@ namespace mtconnect { path << operation; if (uq.size() > 0) path << '?' << uq.join(); - + return path.str(); } - + int getPort() const { if (m_port) @@ -974,7 +987,7 @@ namespace mtconnect { else return 0; } - + /// @brief Format the URL as text /// @param device optional device to add to the URL /// @return formatted URL @@ -986,7 +999,7 @@ namespace mtconnect { url << *device; return url.str(); } - + /// @brief parse a string to a Url /// @return parsed URL static Url parse(const std::string_view &url); diff --git a/test_package/agent_test_helper.cpp b/test_package/agent_test_helper.cpp index b2a466a4a..ba25378aa 100644 --- a/test_package/agent_test_helper.cpp +++ b/test_package/agent_test_helper.cpp @@ -75,7 +75,7 @@ void AgentTestHelper::putResponseHelper(const char *file, int line, const string const char *accepts) { makeRequest(file, line, http::verb::put, body, aQueries, path, accepts); - if (ends_with(m_session->m_mimeType, "xml")) + if (m_session->m_mimeType.ends_with("xml"sv)) *doc = xmlParseMemory(m_session->m_body.c_str(), int32_t(m_session->m_body.size())); } @@ -83,7 +83,7 @@ void AgentTestHelper::deleteResponseHelper(const char *file, int line, const Que xmlDocPtr *doc, const char *path, const char *accepts) { makeRequest(file, line, http::verb::delete_, "", aQueries, path, accepts); - if (ends_with(m_session->m_mimeType, "xml")) + if (m_session->m_mimeType.ends_with("xml"sv)) *doc = xmlParseMemory(m_session->m_body.c_str(), int32_t(m_session->m_body.size())); } diff --git a/test_package/file_cache_test.cpp b/test_package/file_cache_test.cpp index a750463fd..48c5db5af 100644 --- a/test_package/file_cache_test.cpp +++ b/test_package/file_cache_test.cpp @@ -107,13 +107,13 @@ TEST_F(FileCacheTest, base_directory_should_redirect) ASSERT_TRUE(file); ASSERT_EQ("/schemas/none.xsd", file->m_redirect); ASSERT_TRUE(m_cache->hasFile("/schemas")); - ASSERT_TRUE(boost::starts_with(std::string(file->m_buffer), "")); + ASSERT_TRUE(std::string_view(file->m_buffer).starts_with("")); auto file2 = m_cache->getFile("/schemas"); ASSERT_TRUE(file); ASSERT_EQ("/schemas/none.xsd", file2->m_redirect); ASSERT_TRUE(m_cache->hasFile("/schemas")); - ASSERT_TRUE(boost::starts_with(std::string(file->m_buffer), "")); + ASSERT_TRUE(std::string_view(file->m_buffer).starts_with("")); } TEST_F(FileCacheTest, file_cache_should_compress_file) From 70f980f1a2079f598e1eeb1cf7416fe9f386d703 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 13:39:03 +0100 Subject: [PATCH 28/84] Fixed tests to use FatalException --- src/mtconnect/agent.cpp | 566 ++++++------ src/mtconnect/configuration/agent_config.cpp | 11 +- src/mtconnect/configuration/agent_config.hpp | 120 +-- src/mtconnect/configuration/async_context.hpp | 70 +- src/mtconnect/configuration/service.cpp | 4 +- src/mtconnect/device_model/device.cpp | 4 +- src/mtconnect/entity/xml_printer.cpp | 2 +- src/mtconnect/pipeline/pipeline.hpp | 3 +- src/mtconnect/printer/xml_printer.cpp | 2 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 1 - src/mtconnect/sink/rest_sink/server.cpp | 134 +-- .../adapter/agent_adapter/agent_adapter.cpp | 3 +- .../adapter/agent_adapter/session_impl.hpp | 210 ++--- .../source/adapter/mqtt/mqtt_adapter.cpp | 2 +- .../source/adapter/shdr/connector.hpp | 2 +- src/mtconnect/source/source.cpp | 12 +- src/mtconnect/source/source.hpp | 2 +- src/mtconnect/utilities.hpp | 282 +++--- test_package/agent_test.cpp | 860 +++++++++--------- test_package/http_server_test.cpp | 8 +- test_package/json_printer_probe_test.cpp | 6 +- test_package/tls_http_server_test.cpp | 6 +- test_package/websockets_test.cpp | 15 +- test_package/xml_parser_test.cpp | 2 +- 24 files changed, 1164 insertions(+), 1163 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index fee9145e2..52544662e 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -73,26 +73,26 @@ namespace mtconnect { namespace net = boost::asio; namespace fs = boost::filesystem; namespace config = mtconnect::configuration; - + static const string g_unavailable("UNAVAILABLE"); static const string g_available("AVAILABLE"); - + // Agent public methods Agent::Agent(config::AsyncContext &context, const string &deviceXmlPath, const ConfigOptions &options) - : m_options(options), - m_context(context), - m_strand(m_context), - m_xmlParser(make_unique()), - m_schemaVersion(GetOption(options, config::SchemaVersion)), - m_deviceXmlPath(deviceXmlPath), - m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), - GetOption(options, config::CheckpointFrequency).value_or(1000)), - m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), - m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) + : m_options(options), + m_context(context), + m_strand(m_context), + m_xmlParser(make_unique()), + m_schemaVersion(GetOption(options, config::SchemaVersion)), + m_deviceXmlPath(deviceXmlPath), + m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), + GetOption(options, config::CheckpointFrequency).value_or(1000)), + m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), + m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) { using namespace asset; - + CuttingToolArchetype::registerAsset(); CuttingTool::registerAsset(); FileArchetypeAsset::registerAsset(); @@ -102,26 +102,26 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); - + m_assetStorage = make_unique( - GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); + GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); m_versionDeviceXml = IsOptionSet(options, mtconnect::configuration::VersionDeviceXml); m_createUniqueIds = IsOptionSet(options, config::CreateUniqueIds); - + auto jsonVersion = - uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); - + uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); + // Create the Printers m_printers["xml"] = make_unique(m_pretty, m_validation); m_printers["json"] = make_unique(jsonVersion, m_pretty, m_validation); - + if (m_schemaVersion) { m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); } - + auto sender = GetOption(options, config::Sender); if (sender) { @@ -129,51 +129,51 @@ namespace mtconnect { pr->setSenderName(*sender); } } - + void Agent::initialize(pipeline::PipelineContextPtr context) { NAMED_SCOPE("Agent::initialize"); - + m_beforeInitializeHooks.exec(*this); - + m_pipelineContext = context; m_loopback = - std::make_shared("AgentSource", m_strand, context, m_options); - + std::make_shared("AgentSource", m_strand, context, m_options); + auto devices = loadXMLDeviceFile(m_deviceXmlPath); if (!m_schemaVersion) { m_schemaVersion.emplace(StrDefaultSchemaVersion()); } - + m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); - + auto disableAgentDevice = GetOption(m_options, config::DisableAgentDevice); if (!(disableAgentDevice && *disableAgentDevice) && m_intSchemaVersion >= SCHEMA_VERSION(1, 7)) { createAgentDevice(); } - + // For the DeviceAdded event for each device for (auto device : devices) addDevice(device); - + if (m_versionDeviceXml && m_createUniqueIds) versionDeviceXml(); - + loadCachedProbe(); - + m_initialized = true; - + m_afterInitializeHooks.exec(*this); } - + void Agent::initialDataItemObservations() { NAMED_SCOPE("Agent::initialDataItemObservations"); - + if (!m_observationsInitialized) { if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && @@ -183,17 +183,17 @@ namespace mtconnect { for (auto &printer : m_printers) printer.second->setValidation(false); } - + for (auto device : m_deviceIndex) initializeDataItems(device); - + if (m_agentDevice) { for (auto device : m_deviceIndex) { auto d = m_agentDevice->getDeviceDataItem("device_added"); string uuid = *device->getUuid(); - + entity::Properties props {{"VALUE", uuid}}; if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { @@ -201,15 +201,15 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + m_loopback->receive(d, props); } } - + m_observationsInitialized = true; } } - + Agent::~Agent() { m_xmlParser.reset(); @@ -217,36 +217,36 @@ namespace mtconnect { m_sources.clear(); m_agentDevice = nullptr; } - + void Agent::start() { NAMED_SCOPE("Agent::start"); - + if (m_started) { LOG(warning) << "Agent already started."; return; } - + try { m_beforeStartHooks.exec(*this); - + for (auto sink : m_sinks) sink->start(); - + initialDataItemObservations(); - + if (m_agentDevice) { auto d = m_agentDevice->getDeviceDataItem("agent_avail"); m_loopback->receive(d, "AVAILABLE"s); } - + // Start all the sources for (auto source : m_sources) source->start(); - + m_afterStartHooks.exec(*this); } catch (std::runtime_error &e) @@ -254,27 +254,27 @@ namespace mtconnect { LOG(fatal) << "Cannot start server: " << e.what(); throw FatalException(e.what()); } - + m_started = true; } - + void Agent::stop() { NAMED_SCOPE("Agent::stop"); - + if (!m_started) { LOG(warning) << "Agent already stopped."; return; } - + m_beforeStopHooks.exec(*this); - + // Stop all adapter threads... LOG(info) << "Shutting down sources"; for (auto source : m_sources) source->stop(); - + // Signal all observers LOG(info) << "Signaling observers to close sessions"; for (auto di : m_dataItemMap) @@ -283,16 +283,16 @@ namespace mtconnect { if (ldi) ldi->signalObservers(0); } - + LOG(info) << "Shutting down sinks"; for (auto sink : m_sinks) sink->stop(); - + LOG(info) << "Shutting down completed"; - + m_started = false; } - + // --------------------------------------- // Pipeline methods // --------------------------------------- @@ -307,7 +307,7 @@ namespace mtconnect { { if (item.expired()) continue; - + auto di = item.lock(); if (di->hasInitialValue()) { @@ -315,7 +315,7 @@ namespace mtconnect { } } } - + std::lock_guard lock(m_circularBuffer); if (m_circularBuffer.addToBuffer(observation) != 0) { @@ -323,7 +323,7 @@ namespace mtconnect { sink->publish(observation); } } - + void Agent::receiveAsset(asset::AssetPtr asset) { DevicePtr device; @@ -332,14 +332,14 @@ namespace mtconnect { device = findDeviceByUUIDorName(*uuid); else device = getDefaultDevice(); - + if (device && device->getAssetChanged() && device->getAssetRemoved()) { if (asset->getDeviceUuid() && *asset->getDeviceUuid() != *device->getUuid()) { asset->setProperty("deviceUuid", *device->getUuid()); } - + string aid = asset->getAssetId(); if (aid[0] == '@') { @@ -353,16 +353,16 @@ namespace mtconnect { asset->setAssetId(aid); } } - + // Add hash to asset if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) asset->addHash(); - + auto old = m_assetStorage->addAsset(asset); - + for (auto &sink : m_sinks) sink->publish(asset); - + if (device) { DataItemPtr di; @@ -388,7 +388,7 @@ namespace mtconnect { if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { const auto &hash = asset->getProperty("hash"); @@ -397,22 +397,22 @@ namespace mtconnect { props.insert_or_assign("hash", hash); } } - + m_loopback->receive(di, props); } - + updateAssetCounts(device, asset->getType()); } } - + bool Agent::reloadDevices(const std::string &deviceFile) { try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - deviceFile, dynamic_cast(m_printers["xml"].get())); - + deviceFile, dynamic_cast(m_printers["xml"].get())); + if (m_xmlParser->getSchemaVersion() && IntSchemaVersion(*m_xmlParser->getSchemaVersion()) != m_intSchemaVersion) { @@ -420,7 +420,7 @@ namespace mtconnect { LOG(warning) << "Schema version does not match agent schema version, restarting the agent"; return false; } - + bool changed = false; for (auto device : devices) { @@ -428,7 +428,7 @@ namespace mtconnect { } if (changed) loadCachedProbe(); - + return true; } catch (runtime_error &e) @@ -446,7 +446,7 @@ namespace mtconnect { throw FatalException(f.what()); } } - + void Agent::loadDeviceXml(const string &deviceXml, const optional source) { try @@ -468,7 +468,7 @@ namespace mtconnect { cerr << f.what() << endl; } } - + void Agent::loadDevices(list devices, const optional source, bool force) { if (!force && !IsOptionSet(m_options, config::EnableSourceDeviceModels)) @@ -476,7 +476,7 @@ namespace mtconnect { LOG(warning) << "Device updates are disabled, skipping update"; return; } - + auto callback = [=, this](config::AsyncContext &context) { try { @@ -490,10 +490,10 @@ namespace mtconnect { { oldUuid = *oldDev->getUuid(); } - + auto uuid = *device->getUuid(); auto name = *device->getComponentName(); - + changed = receiveDevice(device, true) || changed; if (changed) { @@ -505,7 +505,7 @@ namespace mtconnect { s->setOptions({{config::Device, uuid}}); } } - + for (auto src : m_sources) { auto adapter = std::dynamic_pointer_cast(src); @@ -521,7 +521,7 @@ namespace mtconnect { } } } - + if (changed) loadCachedProbe(); } @@ -548,7 +548,7 @@ namespace mtconnect { cerr << f.what() << endl; } }; - + // Gets around a race condition in the loading of adapaters and setting of // UUID. if (m_context.isRunning() && !m_context.isPauased()) @@ -556,11 +556,11 @@ namespace mtconnect { else callback(m_context); } - + bool Agent::receiveDevice(device_model::DevicePtr device, bool version) { NAMED_SCOPE("Agent::receiveDevice"); - + // diff the device against the current device with the same uuid auto uuid = device->getUuid(); if (!uuid) @@ -568,7 +568,7 @@ namespace mtconnect { LOG(error) << "Device does not have a uuid: " << device->getName(); return false; } - + DevicePtr oldDev = findDeviceByUUIDorName(*uuid); if (!oldDev) { @@ -578,10 +578,10 @@ namespace mtconnect { LOG(error) << "Device does not have a name" << *uuid; return false; } - + oldDev = findDeviceByUUIDorName(*name); } - + // If this is a new device if (!oldDev) { @@ -589,10 +589,10 @@ namespace mtconnect { addDevice(device); if (version) versionDeviceXml(); - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -603,15 +603,15 @@ namespace mtconnect { LOG(error) << "Device does not have a name: " << *device->getUuid(); return false; } - + verifyDevice(device); createUniqueIds(device); - + LOG(info) << "Checking if device " << *uuid << " has changed"; if (device->different(*oldDev)) { LOG(info) << "Device " << *uuid << " changed, updating model"; - + // Remove the old data items set skip; for (auto &di : oldDev->getDeviceDataItems()) @@ -622,7 +622,7 @@ namespace mtconnect { skip.insert(di.lock()->getId()); } } - + // Replace device in device maps auto it = find(m_deviceIndex.begin(), m_deviceIndex.end(), oldDev); if (it != m_deviceIndex.end()) @@ -632,18 +632,18 @@ namespace mtconnect { LOG(error) << "Cannot find Device " << *uuid << " in devices"; return false; } - + initializeDataItems(device, skip); - + LOG(info) << "Device " << *uuid << " updating circular buffer"; m_circularBuffer.updateDataItems(m_dataItemMap); - + if (m_intSchemaVersion > SCHEMA_VERSION(2, 2)) device->addHash(); - + if (version) versionDeviceXml(); - + if (m_agentDevice) { entity::Properties props {{"VALUE", *uuid}}; @@ -653,14 +653,14 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_changed"); m_loopback->receive(d, props); } - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -668,29 +668,29 @@ namespace mtconnect { LOG(info) << "Device " << *uuid << " did not change, ignoring new device"; } } - + return false; } - + void Agent::versionDeviceXml() { NAMED_SCOPE("Agent::versionDeviceXml"); - + using namespace std::chrono; - + if (m_versionDeviceXml) { m_beforeDeviceXmlUpdateHooks.exec(*this); - + // update with a new version of the device.xml, saving the old one // with a date time stamp auto ext = - date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); + date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); fs::path file(m_deviceXmlPath); fs::path backup(m_deviceXmlPath + ext); if (!fs::exists(backup)) fs::rename(file, backup); - + auto printer = getPrinter("xml"); if (printer != nullptr) { @@ -698,11 +698,11 @@ namespace mtconnect { copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list), [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; }); auto probe = printer->printProbe(0, 0, 0, 0, 0, list, nullptr, true, true); - + ofstream devices(file.string()); devices << probe; devices.close(); - + m_afterDeviceXmlUpdateHooks.exec(*this); } else @@ -711,7 +711,7 @@ namespace mtconnect { } } } - + bool Agent::removeAsset(DevicePtr device, const std::string &id, const std::optional time) { @@ -720,10 +720,10 @@ namespace mtconnect { { for (auto &sink : m_sinks) sink->publish(asset); - + notifyAssetRemoved(device, asset); updateAssetCounts(device, asset->getType()); - + return true; } else @@ -731,7 +731,7 @@ namespace mtconnect { return false; } } - + bool Agent::removeAllAssets(const std::optional device, const std::optional type, const std::optional time, asset::AssetList &list) @@ -746,13 +746,13 @@ namespace mtconnect { else uuid = device; } - + auto count = m_assetStorage->removeAll(list, uuid, type, time); for (auto &asset : list) { notifyAssetRemoved(nullptr, asset); } - + if (dev) { updateAssetCounts(dev, type); @@ -762,10 +762,10 @@ namespace mtconnect { for (auto d : m_deviceIndex) updateAssetCounts(d, type); } - + return count > 0; } - + void Agent::notifyAssetRemoved(DevicePtr device, const asset::AssetPtr &asset) { if (device || asset->getDeviceUuid()) @@ -791,7 +791,7 @@ namespace mtconnect { {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); } } - + auto added = dev->getAssetAdded(); if (added) { @@ -804,34 +804,34 @@ namespace mtconnect { } } } - + // --------------------------------------- // Agent Device // --------------------------------------- - + void Agent::createAgentDevice() { NAMED_SCOPE("Agent::createAgentDevice"); - + using namespace boost; - + auto address = GetBestHostAddress(m_context); - + auto port = GetOption(m_options, mtconnect::configuration::Port).value_or(5000); auto service = boost::lexical_cast(port); address.append(":").append(service); - + uuids::name_generator_latest gen(uuids::ns::dns()); auto uuid = GetOption(m_options, mtconnect::configuration::AgentDeviceUUID) - .value_or(uuids::to_string(gen(address))); + .value_or(uuids::to_string(gen(address))); auto id = "agent_"s + uuid.substr(0, uuid.find_first_of('-')); - + // Create the Agent Device ErrorList errors; Properties ps { - {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; + {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; m_agentDevice = - dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); + dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); if (!errors.empty()) { for (auto &e : errors) @@ -840,21 +840,21 @@ namespace mtconnect { } addDevice(m_agentDevice); } - + // ---------------------------------------------- // Device management and Initialization // ---------------------------------------------- - + std::list Agent::loadXMLDeviceFile(const std::string &configXmlPath) { NAMED_SCOPE("Agent::loadXMLDeviceFile"); - + try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - configXmlPath, dynamic_cast(m_printers["xml"].get())); - + configXmlPath, dynamic_cast(m_printers["xml"].get())); + if (!m_schemaVersion && m_xmlParser->getSchemaVersion() && !m_xmlParser->getSchemaVersion()->empty()) { @@ -866,7 +866,7 @@ namespace mtconnect { m_schemaVersion = StrDefaultSchemaVersion(); m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); } - + return devices; } catch (runtime_error &e) @@ -883,14 +883,14 @@ namespace mtconnect { cerr << f.what() << endl; throw FatalException(f.what()); } - + return {}; } - + void Agent::verifyDevice(DevicePtr device) { NAMED_SCOPE("Agent::verifyDevice"); - + // Add the devices to the device map and create availability and // asset changed events if they don't exist // Make sure we have two device level data items: @@ -901,79 +901,79 @@ namespace mtconnect { // Create availability data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make( - {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, - errors); + {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, + errors); device->addDataItem(di, errors); } - + if (!device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 2)) { entity::ErrorList errors; // Create asset change data item and add it to the device. auto di = DataItem::make({{"type", "ASSET_CHANGED"s}, - {"id", device->getId() + "_asset_chg"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_chg"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 5)) { auto di = device->getAssetChanged(); if (!di->isDiscrete()) di->makeDiscrete(); } - + if (!device->getAssetRemoved() && m_intSchemaVersion >= SCHEMA_VERSION(1, 3)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_REMOVED"s}, - {"id", device->getId() + "_asset_rem"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_rem"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetAdded() && m_intSchemaVersion >= SCHEMA_VERSION(2, 6)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_ADDED"s}, - {"id", device->getId() + "_asset_add"}, - {"discrete", true}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_add"}, + {"discrete", true}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetCount() && m_intSchemaVersion >= SCHEMA_VERSION(2, 0)) { entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_COUNT"s}, - {"id", device->getId() + "_asset_count"}, - {"category", "EVENT"s}, - {"representation", "DATA_SET"s}}, + {"id", device->getId() + "_asset_count"}, + {"category", "EVENT"s}, + {"representation", "DATA_SET"s}}, errors); device->addDataItem(di, errors); } - + if (IsOptionSet(m_options, mtconnect::configuration::PreserveUUID)) { device->setPreserveUuid(*GetOption(m_options, mtconnect::configuration::PreserveUUID)); } } - + void Agent::initializeDataItems(DevicePtr device, std::optional> skip) { NAMED_SCOPE("Agent::initializeDataItems"); - + // Initialize the id mapping for the devices and set all data items to UNAVAILABLE for (auto item : device->getDeviceDataItems()) { if (item.expired()) continue; - + auto d = item.lock(); if ((!skip || skip->count(d->getId()) > 0) && m_dataItemMap.count(d->getId()) > 0) { @@ -981,10 +981,10 @@ namespace mtconnect { if (di && di != d) { stringstream msg; - + msg << "Duplicate DataItem id " << d->getId() - << " for device: " << *device->getComponentName() << - ". Try using configuration option CreateUniqueIds to resolve."; + << " for device: " << *device->getComponentName() + << ". Try using configuration option CreateUniqueIds to resolve."; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } @@ -997,18 +997,18 @@ namespace mtconnect { value = &g_unavailable; else if (d->getConstantValue()) value = &d->getConstantValue().value(); - + m_loopback->receive(d, *value); m_dataItemMap[d->getId()] = d; } } } - + // Add the a device from a configuration file void Agent::addDevice(DevicePtr device) { NAMED_SCOPE("Agent::addDevice"); - + // Check if device already exists string uuid = *device->getUuid(); auto &idx = m_deviceIndex.get(); @@ -1018,22 +1018,22 @@ namespace mtconnect { // Update existing device stringstream msg; msg << "Device " << *device->getUuid() << " already exists. " - << " Update not supported yet"; + << " Update not supported yet"; throw msg; } else { m_deviceIndex.push_back(device); - + // TODO: Redo Resolve Reference with entity // device->resolveReferences(); verifyDevice(device); createUniqueIds(device); - + if (m_observationsInitialized) { initializeDataItems(device); - + // Check for single valued constrained data items. if (m_agentDevice && device != m_agentDevice) { @@ -1044,24 +1044,24 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_added"); m_loopback->receive(d, props); } } } - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) device->addHash(); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + void Agent::deviceChanged(DevicePtr device, const std::string &uuid) { NAMED_SCOPE("Agent::deviceChanged"); - + bool changed = false; string oldUuid = *device->getUuid(); if (uuid != oldUuid) @@ -1074,28 +1074,28 @@ namespace mtconnect { m_loopback->receive(d, oldUuid); } } - + if (changed) { // Create a new device auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); auto newDevice = m_xmlParser->parseDevice(xmlPrinter->printDevice(device), xmlPrinter); - + newDevice->setUuid(uuid); - + m_loopback->receive(newDevice); } } - + void Agent::createUniqueIds(DevicePtr device) { if (m_createUniqueIds && !dynamic_pointer_cast(device)) { std::unordered_map idMap; - + device->createUniqueIds(idMap); device->updateReferences(idMap); - + // Update the data item map. for (auto &id : idMap) { @@ -1108,83 +1108,83 @@ namespace mtconnect { } } } - + void Agent::loadCachedProbe() { NAMED_SCOPE("Agent::loadCachedProbe"); - + // Reload the document for path resolution auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); m_xmlParser->loadDocument(xmlPrinter->printProbe(0, 0, 0, 0, 0, getDevices())); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + // ---------------------------------------------------- // Helper Methods // ---------------------------------------------------- - + DevicePtr Agent::getDeviceByName(const std::string &name) const { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::getDeviceByName(const std::string &name) { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::findDeviceByUUIDorName(const std::string &idOrName) const { if (idOrName.empty()) return getDefaultDevice(); - + DevicePtr res; if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; else if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; - + return res; } - + // ---------------------------------------------------- // Adapter Methods // ---------------------------------------------------- - + void Agent::addSource(source::SourcePtr source, bool start) { m_sources.emplace_back(source); - + if (start) source->start(); - + auto adapter = dynamic_pointer_cast(source); if (m_agentDevice && adapter) { m_agentDevice->addAdapter(adapter); - + if (m_observationsInitialized) initializeDataItems(m_agentDevice); - + // Reload the document for path resolution if (m_initialized) { @@ -1192,15 +1192,15 @@ namespace mtconnect { } } } - + void Agent::addSink(sink::SinkPtr sink, bool start) { m_sinks.emplace_back(sink); - + if (start) sink->start(); } - + void AgentPipelineContract::deliverConnectStatus(entity::EntityPtr entity, const StringList &devices, bool autoAvailable) { @@ -1222,15 +1222,15 @@ namespace mtconnect { LOG(error) << "Unexpected connection status received: " << value; } } - + void AgentPipelineContract::deliverCommand(entity::EntityPtr entity) { auto command = entity->get("command"); auto value = entity->getValue(); - + auto device = entity->maybeGet("device"); auto source = entity->maybeGet("source"); - + if ((command != "devicemodel" && !device) || !source) { LOG(error) << "Invalid command: " << command << ", device or source not specified"; @@ -1243,7 +1243,7 @@ namespace mtconnect { m_agent->receiveCommand(*device, command, value, *source); } } - + void Agent::connecting(const std::string &adapter) { if (m_agentDevice) @@ -1253,30 +1253,30 @@ namespace mtconnect { m_loopback->receive(di, "LISTEN"); } } - + // Add values for related data items UNAVAILABLE void Agent::disconnected(const std::string &adapter, const StringList &devices, bool autoAvailable) { LOG(debug) << "Disconnected from adapter, setting all values to UNAVAILABLE"; - + if (m_agentDevice) { auto di = m_agentDevice->getConnectionStatus(adapter); if (di) m_loopback->receive(di, "CLOSED"); } - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); if (device == nullptr) { LOG(warning) << "Cannot find device " << name << " when adapter " << adapter - << "disconnected"; + << "disconnected"; continue; } - + for (auto di : device->getDeviceDataItems()) { auto dataItem = di.lock(); @@ -1285,7 +1285,7 @@ namespace mtconnect { dataItem->getType() == "AVAILABILITY"))) { auto ptr = getLatest(dataItem->getId()); - + if (ptr) { const string *value = nullptr; @@ -1293,7 +1293,7 @@ namespace mtconnect { value = &dataItem->getConstantValue().value(); else if (!ptr->isUnavailable()) value = &g_unavailable; - + if (value) m_loopback->receive(dataItem, *value); } @@ -1303,7 +1303,7 @@ namespace mtconnect { } } } - + void Agent::connected(const std::string &adapter, const StringList &devices, bool autoAvailable) { if (m_agentDevice) @@ -1312,10 +1312,10 @@ namespace mtconnect { if (di) m_loopback->receive(di, "ESTABLISHED"); } - + if (!autoAvailable) return; - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); @@ -1325,7 +1325,7 @@ namespace mtconnect { continue; } LOG(debug) << "Connected to adapter, setting all Availability data items to AVAILABLE"; - + if (auto avail = device->getAvailability()) { LOG(debug) << "Adding availabilty event for " << device->getAvailability()->getId(); @@ -1335,7 +1335,7 @@ namespace mtconnect { LOG(debug) << "Cannot find availability for " << *device->getComponentName(); } } - + void Agent::sourceFailed(const std::string &identity) { auto source = findSource(identity); @@ -1343,7 +1343,7 @@ namespace mtconnect { { source->stop(); m_sources.remove(source); - + bool ext = false; for (auto &s : m_sources) { @@ -1353,7 +1353,7 @@ namespace mtconnect { break; } } - + if (!ext) { LOG(fatal) << "Source " << source->getName() << " failed"; @@ -1371,16 +1371,16 @@ namespace mtconnect { LOG(error) << "Cannot find failed source: " << source->getName(); } } - + // ----------------------------------------------- // Validation methods // ----------------------------------------------- - + string Agent::devicesAndPath(const std::optional &path, const DevicePtr device, const std::optional &deviceType) const { string dataPath; - + if (device || deviceType) { string prefix; @@ -1390,16 +1390,16 @@ namespace mtconnect { prefix = "//Devices/Device[@uuid=\"" + *device->getUuid() + "\"]"; else if (deviceType) prefix = "//Devices/Device"; - + if (path) { stringstream parse(*path); string token; - + // Prefix path (i.e. "path1|path2" => "{prefix}path1|{prefix}path2") while (getline(parse, token, '|')) dataPath += prefix + token + "|"; - + dataPath.erase(dataPath.length() - 1); } else @@ -1414,10 +1414,10 @@ namespace mtconnect { else dataPath = "//Devices/Device|//Devices/Agent"; } - + return dataPath; } - + void AgentPipelineContract::deliverAssetCommand(entity::EntityPtr command) { const std::string &cmd = command->getValue(); @@ -1442,33 +1442,33 @@ namespace mtconnect { LOG(error) << "Invalid assent command: " << cmd; } } - + void Agent::updateAssetCounts(const DevicePtr &device, const std::optional type) { if (!device) return; - + auto dc = device->getAssetCount(); if (dc) { if (type) { auto count = m_assetStorage->getCountForDeviceAndType(*device->getUuid(), *type); - + DataSet set; if (count > 0) set.emplace(*type, int64_t(count)); else set.emplace(*type, DataSetValue(), true); - + m_loopback->receive(dc, {{"VALUE", set}}); } else { auto counts = m_assetStorage->getCountsByTypeForDevice(*device->getUuid()); - + DataSet set; - + for (auto &[t, count] : counts) { if (count > 0) @@ -1476,67 +1476,67 @@ namespace mtconnect { else set.emplace(t, DataSetValue(), true); } - + m_loopback->receive(dc, {{"resetTriggered", "RESET_COUNTS"s}, {"VALUE", set}}); } } } - + void Agent::receiveCommand(const std::string &deviceName, const std::string &command, const std::string &value, const std::string &source) { DevicePtr device {nullptr}; device = findDeviceByUUIDorName(deviceName); - + if (!device) { LOG(warning) << source << ": Cannot find device for name " << deviceName; } - + static std::unordered_map> - deviceCommands { - {"manufacturer", mem_fn(&Device::setManufacturer)}, - {"station", mem_fn(&Device::setStation)}, - {"serialnumber", mem_fn(&Device::setSerialNumber)}, - {"description", mem_fn(&Device::setDescriptionValue)}, - {"nativename", - [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, - {"calibration", [](DevicePtr device, const string &value) { - istringstream line(value); - - // Look for name|factor|offset triples - string name, factor, offset; - while (getline(line, name, '|') && getline(line, factor, '|') && - getline(line, offset, '|')) - { - // Convert to a floating point number - auto di = device->getDeviceDataItem(name); - if (!di) - LOG(warning) << "Cannot find data item to calibrate for " << name; - else - { - try - { - double fact_value = stod(factor); - double off_value = stod(offset); - - device_model::data_item::UnitConversion conv(fact_value, off_value); - di->setConverter(conv); - } - catch (std::exception e) - { - LOG(error) << "Cannot convert factor " << factor << " or " << offset - << " to double: " << e.what(); - } - } - } - }}}; - + deviceCommands { + {"manufacturer", mem_fn(&Device::setManufacturer)}, + {"station", mem_fn(&Device::setStation)}, + {"serialnumber", mem_fn(&Device::setSerialNumber)}, + {"description", mem_fn(&Device::setDescriptionValue)}, + {"nativename", + [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, + {"calibration", [](DevicePtr device, const string &value) { + istringstream line(value); + + // Look for name|factor|offset triples + string name, factor, offset; + while (getline(line, name, '|') && getline(line, factor, '|') && + getline(line, offset, '|')) + { + // Convert to a floating point number + auto di = device->getDeviceDataItem(name); + if (!di) + LOG(warning) << "Cannot find data item to calibrate for " << name; + else + { + try + { + double fact_value = stod(factor); + double off_value = stod(offset); + + device_model::data_item::UnitConversion conv(fact_value, off_value); + di->setConverter(conv); + } + catch (std::exception e) + { + LOG(error) << "Cannot convert factor " << factor << " or " << offset + << " to double: " << e.what(); + } + } + } + }}}; + static std::unordered_map adapterDataItems { - {"adapterversion", "_adapter_software_version"}, - {"mtconnectversion", "_mtconnect_version"}, + {"adapterversion", "_adapter_software_version"}, + {"mtconnectversion", "_mtconnect_version"}, }; - + if (command == "devicemodel") { loadDeviceXml(value, source); @@ -1578,7 +1578,7 @@ namespace mtconnect { else { LOG(warning) << "Cannot find data item for the Agent device when processing command " - << command << " with value " << value << " for adapter " << source; + << command << " with value " << value << " for adapter " << source; } } } @@ -1592,10 +1592,10 @@ namespace mtconnect { else { LOG(error) << source << ":Received protocol command '" << command << "' for device '" - << deviceName << "', but the device could not be found"; + << deviceName << "', but the device could not be found"; } } - + // ------------------------------------------- // End // ------------------------------------------- diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index a42f93f9d..2b6b9a67c 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -880,11 +880,11 @@ namespace mtconnect::configuration { logPaths(LOG_LEVEL(fatal), m_configPaths); throw FatalException(((string) "Please make sure the configuration " - "file probe.xml or Devices.xml is in the current " - "directory or specify the correct file " - "in the configuration file " + - m_configFile.string() + " using Devices = ") - .c_str()); + "file probe.xml or Devices.xml is in the current " + "directory or specify the correct file " + "in the configuration file " + + m_configFile.string() + " using Devices = ") + .c_str()); } m_name = get(options[configuration::ServiceName]); @@ -1252,4 +1252,3 @@ namespace mtconnect::configuration { return false; } } // namespace mtconnect::configuration - diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 347123fa1..593ae6dca 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -47,7 +47,7 @@ namespace mtconnect { namespace device_model { class Device; } - + #ifdef WITH_PYTHON namespace python { class Embedded; @@ -58,13 +58,13 @@ namespace mtconnect { class Embedded; } #endif - + class XmlPrinter; - + /// @brief Configuration namespace namespace configuration { using DevicePtr = std::shared_ptr; - + /// @brief Parses the configuration file and creates the `Agent`. Manages config /// file tracking and restarting of the agent. class AGENT_LIB_API AgentConfiguration : public MTConnectService @@ -77,19 +77,19 @@ namespace mtconnect { XML, UNKNOWN }; - + using InitializationFn = void(const boost::property_tree::ptree &, AgentConfiguration &); using InitializationFunction = boost::function; - + using ptree = boost::property_tree::ptree; - + /// @brief Construct the agent configuration AgentConfiguration(); virtual ~AgentConfiguration(); - + /// @name Callbacks for initialization phases ///@{ - + /// @brief Get the callback manager after the agent is created /// @return the callback manager auto &afterAgentHooks() { return m_afterAgentHooks; } @@ -100,7 +100,7 @@ namespace mtconnect { /// @return the callback manager auto &beforeStopHooks() { return m_beforeStopHooks; } ///@} - + /// @brief stops the agent. Used in daemons. void stop() override; /// @brief starts the agent. Used in daemons. @@ -109,24 +109,24 @@ namespace mtconnect { /// @brief initializes the configuration of the agent from the command line parameters /// @param[in] options command line parameters void initialize(const boost::program_options::variables_map &options) override; - + /// @brief Configure the logger with the config node from the config file /// @param channelName the log channel name /// @param config the configuration node /// @param formatter optional custom message format void configureLoggerChannel( - const std::string &channelName, const ptree &config, - std::optional> formatter = std::nullopt); - + const std::string &channelName, const ptree &config, + std::optional> formatter = std::nullopt); + /// @brief Configure the agent logger with the config node from the config file /// @param config the configuration node void configureLogger(const ptree &config); - + /// @brief load a configuration text /// @param[in] text the configuration text loaded from a file /// @param[in] fmt the file format, can be MTCONNECT, JSON, or XML void loadConfig(const std::string &text, FileFormat fmt = MTCONNECT); - + /// @brief assign the agent associated with this configuration /// @param[in] agent the agent the configuration will take ownership of void setAgent(std::unique_ptr &agent) { m_agent = std::move(agent); } @@ -136,10 +136,10 @@ namespace mtconnect { auto &getContext() { return m_context->get(); } /// @brief get a pointer to the async io manager auto &getAsyncContext() { return *m_context.get(); } - + /// @brief sets the path for the working directory to the current path void updateWorkingDirectory() { m_working = std::filesystem::current_path(); } - + /// @name Configuration factories ///@{ /// @brief get the factory for creating sinks @@ -149,11 +149,11 @@ namespace mtconnect { /// @return the factory auto &getSourceFactory() { return m_sourceFactory; } ///@} - + /// @brief get the pipeline context for this configuration /// @note set after the agent is created auto getPipelineContext() { return m_pipelineContext; } - + /// @name Logging methods ///@{ /// @brief gets the boost log sink @@ -210,7 +210,7 @@ namespace mtconnect { { return m_logChannels[channelName].m_logLevel; } - + /// @brief set the logging level /// @param[in] level the new logging level void setLoggingLevel(const boost::log::trivial::severity_level level); @@ -218,62 +218,62 @@ namespace mtconnect { /// @param level the new logging level /// @return the logging level boost::log::trivial::severity_level setLoggingLevel(const std::string &level); - + std::optional findConfigFile(const std::string &file) { return findFile(m_configPaths, file); } - + std::optional findDataFile(const std::string &file) { return findFile(m_dataPaths, file); } - + /// @brief Create a sink contract with functions to find config and data files. /// @return shared pointer to a sink contract sink::SinkContractPtr makeSinkContract() { auto contract = m_agent->makeSinkContract(); contract->m_findConfigFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findConfigFile(n); }; contract->m_findDataFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findDataFile(n); }; return contract; } - + /// @brief add a path to the config paths /// @param path the path to add void addConfigPath(const std::filesystem::path &path) { addPathBack(m_configPaths, path); } - + /// @brief add a path to the data paths /// @param path the path to add void addDataPath(const std::filesystem::path &path) { addPathBack(m_dataPaths, path); } - + /// @brief add a path to the plugin paths /// @param path the path to add void addPluginPath(const std::filesystem::path &path) { addPathBack(m_pluginPaths, path); } - + protected: DevicePtr getDefaultDevice(); void loadAdapters(const ptree &tree, const ConfigOptions &options); void loadSinks(const ptree &sinks, ConfigOptions &options); - + #ifdef WITH_PYTHON void configurePython(const ptree &tree, ConfigOptions &options); #endif #ifdef WITH_RUBY void configureRuby(const ptree &tree, ConfigOptions &options); #endif - + void loadPlugins(const ptree &tree); bool loadPlugin(const std::string &name, const ptree &tree); void monitorFiles(boost::system::error_code ec); void scheduleMonitorTimer(); - + protected: std::optional findFile(const std::list &paths, const std::string file) @@ -285,33 +285,33 @@ namespace mtconnect { if (std::filesystem::exists(tst, ec) && !ec) { LOG(debug) << "Found file '" << file << "' " - << " in path " << path; + << " in path " << path; auto con {std::filesystem::canonical(tst)}; return con; } else { LOG(debug) << "Cannot find file '" << file << "' " - << " in path " << path; + << " in path " << path; } } - + return std::nullopt; } - + void addPathBack(std::list &paths, std::filesystem::path path) { std::error_code ec; auto con {std::filesystem::canonical(path, ec)}; if (std::find(paths.begin(), paths.end(), con) != paths.end()) return; - + if (!ec) paths.emplace_back(con); else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + void addPathFront(std::list &paths, std::filesystem::path path) { std::error_code ec; @@ -322,7 +322,7 @@ namespace mtconnect { else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + template void logPaths(T lvl, const std::list &paths) { @@ -330,52 +330,52 @@ namespace mtconnect { { BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(), (::boost::log::keywords::severity = lvl)) - << " " << p; + << " " << p; } } - + void expandConfigVariables(boost::property_tree::ptree &); - + protected: using text_sink = boost::log::sinks::synchronous_sink; using console_sink = - boost::log::sinks::synchronous_sink; - + boost::log::sinks::synchronous_sink; + struct LogChannel { std::string m_channelName; std::filesystem::path m_logDirectory; std::filesystem::path m_logArchivePattern; std::filesystem::path m_logFileName; - + int64_t m_maxLogFileSize {0}; int64_t m_logRotationSize {0}; int64_t m_rotationLogInterval {0}; - + boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; - + boost::shared_ptr m_logSink; }; - + std::map m_logChannels; - + std::map m_initializers; - + std::unique_ptr m_context; std::unique_ptr m_agent; - + pipeline::PipelineContextPtr m_pipelineContext; std::unique_ptr m_adapterHandler; - + std::string m_version; std::string m_devicesFile; std::filesystem::path m_exePath; std::filesystem::path m_working; - + std::list m_configPaths; std::list m_dataPaths; std::list m_pluginPaths; - + // File monitoring boost::asio::steady_timer m_monitorTimer; bool m_monitorFiles = false; @@ -384,23 +384,23 @@ namespace mtconnect { bool m_restart = false; std::optional m_configTime; std::optional m_deviceTime; - + // Factories sink::SinkFactory m_sinkFactory; source::SourceFactory m_sourceFactory; - + int m_workerThreadCount {1}; - + // Reference to the global logger boost::log::trivial::logger_type *m_logger {nullptr}; - + #ifdef WITH_RUBY std::unique_ptr m_ruby; #endif #ifdef WITH_PYTHON std::unique_ptr m_python; #endif - + HookManager m_afterAgentHooks; HookManager m_beforeStartHooks; HookManager m_beforeStopHooks; diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp index c13b037cf..6c4694913 100644 --- a/src/mtconnect/configuration/async_context.hpp +++ b/src/mtconnect/configuration/async_context.hpp @@ -20,12 +20,12 @@ #include #include -#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" +#include "mtconnect/utilities.hpp" namespace mtconnect::configuration { - + /// @brief Manages the boost asio context and allows for a syncronous /// callback to execute when all the worker threads have stopped. class AGENT_LIB_API AsyncContext @@ -33,36 +33,36 @@ namespace mtconnect::configuration { public: using SyncCallback = std::function; using WorkGuard = boost::asio::executor_work_guard; - + /// @brief creates an asio context and a guard to prevent it from /// stopping AsyncContext() { m_guard.emplace(m_context.get_executor()); } /// @brief removes the copy constructor AsyncContext(const AsyncContext &) = delete; ~AsyncContext() {} - + /// @brief is the context running /// @returns running status auto isRunning() { return m_running; } - + /// @brief return the paused state /// @returns the paused state auto isPauased() { return m_paused; } - + /// @brief Testing only: method to remove the run guard from the context void removeGuard() { m_guard.reset(); } - + /// @brief get the boost asio context reference auto &get() { return m_context; } - + /// @brief operator() returns a reference to the io context /// @return the io context operator boost::asio::io_context &() { return m_context; } - + /// @brief sets the number of theads for asio thread pool /// @param[in] threads number of threads void setThreadCount(int threads) { m_threadCount = threads; } - + /// @brief start `m_threadCount` worker threads /// @returns the exit code int start() @@ -76,13 +76,12 @@ namespace mtconnect::configuration { { for (int i = 0; i < m_threadCount; i++) { - m_workers.emplace_back(boost::thread([this]() - { + m_workers.emplace_back(boost::thread([this]() { try { m_context.run(); } - + catch (FatalException &e) { LOG(fatal) << "Fatal exception occurred: " << e.what(); @@ -101,7 +100,6 @@ namespace mtconnect::configuration { stop(); m_exitCode = 1; } - })); } auto &first = m_workers.front(); @@ -113,16 +111,16 @@ namespace mtconnect::configuration { m_context.stop(); } } - + for (auto &w : m_workers) { w.join(); } m_workers.clear(); - + if (m_delayedStop.joinable()) m_delayedStop.join(); - + if (m_syncCallback && m_exitCode == 0) { m_syncCallback(*this); @@ -132,10 +130,10 @@ namespace mtconnect::configuration { restart(); } } - + } while (m_running); } - + catch (FatalException &e) { LOG(fatal) << "Fatal exception occurred: " << e.what(); @@ -154,10 +152,10 @@ namespace mtconnect::configuration { stop(); m_exitCode = 1; } - + return m_exitCode; } - + /// @brief pause the worker threads. Sets a callback when the threads are paused. /// @param[in] callback the callback to call /// @param[in] safeStop stops by resetting the the guard, otherwise stop the @@ -171,7 +169,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief stop the worker threads /// @param safeStop if `true` resets the guard or stops the context void stop(bool safeStop = true) @@ -182,7 +180,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief restarts the worker threads when paused void restart() { @@ -191,59 +189,59 @@ namespace mtconnect::configuration { m_guard.emplace(m_context.get_executor()); m_context.restart(); } - + /// @name Cover methods for asio io_context /// @{ - + /// @brief io_context::run_for template auto run_for(const std::chrono::duration &rel_time) { return m_context.run_for(rel_time); } - + /// @brief io_context::run auto run() { return m_context.run(); } - + /// @brief io_context::run_one auto run_one() { return m_context.run_one(); } - + /// @brief io_context::run_one_for template auto run_one_for(const std::chrono::duration &rel_time) { return m_context.run_one_for(rel_time); } - + /// @brief io_context::run_one_until template auto run_one_until(const std::chrono::time_point &abs_time) { return m_context.run_one_for(abs_time); } - + /// @brief io_context::poll auto poll() { return m_context.poll(); } - + /// @brief io_context::poll auto get_executor() BOOST_ASIO_NOEXCEPT { return m_context.get_executor(); } - + /// @} - + private: void operator=(const AsyncContext &) {} - + protected: boost::asio::io_context m_context; std::list m_workers; SyncCallback m_syncCallback; std::optional m_guard; std::thread m_delayedStop; - + int m_threadCount = 1; bool m_running = false; bool m_paused = false; int m_exitCode = 0; }; - + } // namespace mtconnect::configuration diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index 4c47c95ff..1709937e7 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -177,7 +177,7 @@ namespace mtconnect { using namespace std; int res = 0; - + try { // If command-line parameter is "install", install the service. If debug or run @@ -238,7 +238,7 @@ namespace mtconnect { { LOG(fatal) << "Agent top level exception: " << e.what(); std::cerr << "Agent top level exception: " << e.what() << std::endl; - res = 1; + res = 1; } catch (std::string &s) { diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index b5c2bbd1c..a6e7a9250 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -62,7 +62,7 @@ namespace mtconnect { { stringstream msg; msg << "Device " << getName() << ": Duplicatie data item id '" << di->getId() - << "', Exiting"; + << "', Exiting"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } @@ -97,7 +97,7 @@ namespace mtconnect { { stringstream msg; msg << "Device " << getName() << ": DataItem '" << di->getId() - << " could not be added, exiting"; + << " could not be added, exiting"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp index 3cfb0ccb4..66d461886 100644 --- a/src/mtconnect/entity/xml_printer.cpp +++ b/src/mtconnect/entity/xml_printer.cpp @@ -19,8 +19,8 @@ #include -#include #include +#include #include "mtconnect/logging.hpp" #include "mtconnect/printer/xml_printer_helper.hpp" diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp index 8fa7b8594..a6ce14c67 100644 --- a/src/mtconnect/pipeline/pipeline.hpp +++ b/src/mtconnect/pipeline/pipeline.hpp @@ -17,9 +17,10 @@ #pragma once -#include #include +#include + #include "mtconnect/config.hpp" #include "pipeline_context.hpp" #include "pipeline_contract.hpp" diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 3ea09f277..06c0da048 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include "mtconnect/asset/asset.hpp" #include "mtconnect/asset/cutting_tool.hpp" diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 41108936f..4bdfbaad2 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1616,4 +1616,3 @@ namespace mtconnect { } // namespace sink::rest_sink } // namespace mtconnect - diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp index b6b23707d..b05804866 100644 --- a/src/mtconnect/sink/rest_sink/server.cpp +++ b/src/mtconnect/sink/rest_sink/server.cpp @@ -43,12 +43,12 @@ namespace mtconnect::sink::rest_sink { using tcp = boost::asio::ip::tcp; namespace algo = boost::algorithm; namespace ssl = boost::asio::ssl; - + using namespace std; using namespace rapidjson; using std::placeholders::_1; using std::placeholders::_2; - + void Server::loadTlsCertificate() { if (HasOption(m_options, configuration::TlsCertificateChain) && @@ -59,27 +59,27 @@ namespace mtconnect::sink::rest_sink { if (HasOption(m_options, configuration::TlsCertificatePassword)) { m_sslContext.set_password_callback( - [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { - return *GetOption(m_options, configuration::TlsCertificatePassword); - }); + [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { + return *GetOption(m_options, configuration::TlsCertificatePassword); + }); } - + m_sslContext.set_options(ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); m_sslContext.use_certificate_chain_file( - *GetOption(m_options, configuration::TlsCertificateChain)); + *GetOption(m_options, configuration::TlsCertificateChain)); m_sslContext.use_private_key_file(*GetOption(m_options, configuration::TlsPrivateKey), asio::ssl::context::file_format::pem); m_sslContext.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - + m_tlsEnabled = true; - + m_tlsOnly = IsOptionSet(m_options, configuration::TlsOnly); - + if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) { LOG(info) << "Will only accept client connections with valid certificates"; - + m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); if (HasOption(m_options, configuration::TlsClientCAs)) { @@ -89,7 +89,7 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::start() { try @@ -103,14 +103,14 @@ namespace mtconnect::sink::rest_sink { throw FatalException(e.what()); } } - + // Listen for an HTTP server connection void Server::listen() { NAMED_SCOPE("Server::listen"); - + beast::error_code ec; - + // Blocking call to listen for a connection tcp::endpoint ep(m_address, m_port); m_acceptor.open(ep.protocol(), ec); @@ -135,23 +135,23 @@ namespace mtconnect::sink::rest_sink { { m_port = m_acceptor.local_endpoint().port(); } - + m_acceptor.listen(net::socket_base::max_listen_connections, ec); if (ec) { fail(ec, "Cannot set listen queue length"); return; } - + m_listening = true; m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } - + bool Server::allowPutFrom(const std::string &host) { NAMED_SCOPE("Server::allowPutFrom"); - + // Resolve the host to an ip address to verify remote addr beast::error_code ec; ip::tcp::resolver resolve(m_context); @@ -162,25 +162,25 @@ namespace mtconnect::sink::rest_sink { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); return false; } - + // Add the results to the set of allowed hosts for (auto &addr : results) { m_allowPutsFrom.insert(addr.endpoint().address()); } m_allowPuts = true; - + return true; } - + void Server::accept(beast::error_code ec, tcp::socket socket) { NAMED_SCOPE("Server::accept"); - + if (ec) { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); - + fail(ec, "Accept failed"); } else if (m_run) @@ -188,7 +188,7 @@ namespace mtconnect::sink::rest_sink { auto dispatcher = [this](SessionPtr session, RequestPtr request) { if (!m_run) return false; - + if (m_lastSession) m_lastSession(session); dispatch(session, request); @@ -197,9 +197,9 @@ namespace mtconnect::sink::rest_sink { if (m_tlsEnabled) { auto dectector = - make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, - m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); - + make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, + m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); + dectector->run(); } else @@ -208,34 +208,34 @@ namespace mtconnect::sink::rest_sink { boost::beast::tcp_stream stream(std::move(socket)); auto session = make_shared(std::move(stream), std::move(buffer), m_fields, dispatcher, m_errorFunction); - + if (!m_allowPutsFrom.empty()) session->allowPutsFrom(m_allowPutsFrom); else if (m_allowPuts) session->allowPuts(); - + session->run(); } m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } } - + //------------------------------------------------------------------------------ - + // Report a failure void Server::fail(beast::error_code ec, char const *what) { LOG(error) << " error: " << ec.message(); } - + using namespace mtconnect::printer; - + template void AddParameter(T &writer, const Parameter ¶m) { AutoJsonObject obj(writer); - + obj.AddPairs("name", param.m_name, "in", param.m_part == PATH ? "path" : "query", "required", param.m_part == PATH); { @@ -245,23 +245,23 @@ namespace mtconnect::sink::rest_sink { case ParameterType::STRING: obj.AddPairs("type", "string", "format", "string"); break; - + case ParameterType::INTEGER: obj.AddPairs("type", "integer", "format", "int64"); break; - + case ParameterType::UNSIGNED_INTEGER: obj.AddPairs("type", "integer", "format", "uint64"); break; - + case ParameterType::DOUBLE: obj.AddPairs("type", "double", "format", "double"); break; - + case ParameterType::BOOL: obj.AddPairs("type", "boolean", "format", "bool"); break; - + case ParameterType::NONE: obj.AddPairs("type", "unknown", "format", "unknown"); break; @@ -270,30 +270,30 @@ namespace mtconnect::sink::rest_sink { { obj.Key("default"); visit( - overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, - [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, - [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, - param.m_default); + overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, + [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, + [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, + param.m_default); } } if (param.m_description) obj.AddPairs("description", *param.m_description); } - + template void AddRouting(T &writer, const Routing &routing) { string verb {to_string(routing.getVerb())}; boost::to_lower(verb); - + { AutoJsonObject obj(writer, verb.data()); - + if (routing.getSummary()) obj.AddPairs("summary", *routing.getSummary()); if (routing.getDescription()) obj.AddPairs("description", *routing.getDescription()); - + if (!routing.getPathParameters().empty() || !routing.getQueryParameters().empty()) { AutoJsonArray ary(writer, "parameters"); @@ -306,7 +306,7 @@ namespace mtconnect::sink::rest_sink { AddParameter(writer, param); } } - + { AutoJsonObject obj(writer, "responses"); { @@ -326,36 +326,36 @@ namespace mtconnect::sink::rest_sink { } } } - + // Swagger stuff template const void Server::renderSwaggerResponse(T &writer) { { AutoJsonObject obj(writer); - + obj.AddPairs("openapi", "3.0.0"); - + { AutoJsonObject obj(writer, "info"); obj.AddPairs("title", "MTConnect – REST API", "description", "MTConnect REST API "); - + { AutoJsonObject obj(writer, "contact"); obj.AddPairs("email", "will@metalogi.io"); } { AutoJsonObject obj(writer, "license"); - + obj.AddPairs("name", "Apache 2.0", "url", "http://www.apache.org/licenses/LICENSE-2.0.html"); } - + obj.AddPairs("version", GetAgentVersion()); } { AutoJsonObject obj(writer, "externalDocs"); - + obj.AddPairs("description", "For information related to MTConnect", "url", "http://mtconnect.org"); } @@ -363,27 +363,27 @@ namespace mtconnect::sink::rest_sink { AutoJsonArray ary(writer, "servers"); { AutoJsonObject obj(writer); - + stringstream str; if (m_tlsEnabled) str << "https://"; else str << "http://"; - + str << GetBestHostAddress(m_context, true) << ':' << m_port << '/'; obj.AddPairs("url", str.str()); } } { AutoJsonObject obj(writer, "paths"); - + multimap routings; for (const auto &routing : m_routings) { if (!routing.isSwagger() && routing.getPath()) routings.emplace(make_pair(*routing.getPath(), &routing)); } - + AutoJsonObject robj(writer, false); for (const auto &[path, routing] : routings) { @@ -394,23 +394,23 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::addSwaggerRoutings() { auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { auto pretty = *request->parameter("pretty"); - + StringBuffer output; RenderJson(output, pretty, [this](auto &writer) { renderSwaggerResponse(writer); }); - + session->writeResponse( - make_unique(status::ok, string(output.GetString()), "application/json")); - + make_unique(status::ok, string(output.GetString()), "application/json")); + return true; }; - + addRouting({boost::beast::http::verb::get, "/swagger?pretty={bool:false}", handler, true}); // addRouting({boost::beast::http::verb::get, "/swagger.yaml", handler, true}); } - + } // namespace mtconnect::sink::rest_sink diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index 4beda3b69..14fa7124e 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -125,7 +125,6 @@ namespace mtconnect::source::adapter::agent_adapter { } else if (device || HasOption(m_options, configuration::SourceDevice)) { - m_sourceDevice = GetOption(m_options, configuration::SourceDevice); if (!m_sourceDevice) m_sourceDevice = device; @@ -138,7 +137,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_name = m_url.getUrlText(m_sourceDevice); m_identity = CreateIdentityHash(m_name); - + m_options.insert_or_assign(configuration::AdapterIdentity, m_identity); m_feedbackId = "XmlTransformFeedback:" + m_identity; diff --git a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp index 02765498a..227ab16fb 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp @@ -25,10 +25,10 @@ #include #include -#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/pipeline/mtconnect_xml_transform.hpp" #include "mtconnect/pipeline/response_document.hpp" +#include "mtconnect/utilities.hpp" #include "session.hpp" namespace mtconnect::source::adapter::agent_adapter { @@ -38,7 +38,7 @@ namespace mtconnect::source::adapter::agent_adapter { namespace http = boost::beast::http; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; - + /// @brief A session implementation that where the derived classes can support HTTP or HTTPS /// @tparam Derived template @@ -46,7 +46,7 @@ namespace mtconnect::source::adapter::agent_adapter { { /// @brief A list of HTTP requests using RequestQueue = std::list; - + public: /// @brief Cast this class as the derived class /// @return reference to the derived class @@ -54,22 +54,22 @@ namespace mtconnect::source::adapter::agent_adapter { /// @brief Immutably cast this class as its derived subclass /// @return const reference to the derived class const Derived &derived() const { return static_cast(*this); } - + // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. SessionImpl(boost::asio::io_context::strand &strand, const url::Url &url) - : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) + : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) {} - + virtual ~SessionImpl() { stop(); } - + /// @brief see if the socket is connected /// @return `true` if the socket is open bool isOpen() const override { return derived().lowestLayer().socket().is_open(); } - + /// @brief close the connection void close() override { derived().lowestLayer().socket().close(); } - + /// @brief Method called when a request fails /// /// Closes the socket and resets the request @@ -78,7 +78,7 @@ namespace mtconnect::source::adapter::agent_adapter { void failed(std::error_code ec, const char *what) override { derived().lowestLayer().socket().close(); - + LOG(error) << "Agent Adapter Connection Failed: " << m_url.getUrlText(nullopt); if (m_request) LOG(error) << "Agent Adapter Target: " << m_request->getTarget(m_url); @@ -87,15 +87,15 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_failed) m_failed(ec); } - + void stop() override { m_request.reset(); } - + bool makeRequest(const Request &req) override { if (!m_request) { m_request.emplace(req); - + // Clean out any previous data m_buffer.clear(); m_contentType.clear(); @@ -107,7 +107,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_hasHeader = false; if (m_chunk.size() > 0) m_chunk.consume(m_chunk.size()); - + // Check if we are discussected. if (!derived().lowestLayer().socket().is_open()) { @@ -127,7 +127,7 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } } - + /// @brief Process data from the remote agent /// @param data the payload from the agent void processData(const std::string &data) @@ -162,7 +162,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Unknown exception in AgentAdapter::processData"); } } - + /// @brief Connect to the remote agent virtual void connect() { @@ -175,26 +175,26 @@ namespace mtconnect::source::adapter::agent_adapter { else if (holds_alternative(m_url.m_host)) { asio::ip::tcp::endpoint ep(get(m_url.m_host), m_url.getPort()); - + // Create the results type and call on resolve directly. using results_type = tcp::resolver::results_type; auto results = results_type::create(ep, m_url.getHost(), m_url.getService()); - + beast::error_code ec; onResolve(ec, results); } else { derived().lowestLayer().expires_after(m_timeout); - + // Do an async resolution of the address. m_resolver.async_resolve( - get(m_url.m_host), m_url.getService(), - asio::bind_executor( - m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); + get(m_url.m_host), m_url.getService(), + asio::bind_executor( + m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); } } - + /// @brief Callback when the host name needs to be resolved /// @param ec error code if resultion fails /// @param results the resolution results @@ -207,34 +207,34 @@ namespace mtconnect::source::adapter::agent_adapter { LOG(error) << " Reason: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(source::ErrorCode::ADAPTER_FAILED), "resolve"); } - + if (!m_request) { LOG(error) << "Resolved but no request"; return; } - + if (!m_resolution) m_resolution.emplace(results); - + if (m_handler && m_handler->m_connecting) m_handler->m_connecting(m_identity); - + // Set a timeout on the operation derived().lowestLayer().expires_after(m_timeout); - + // Make the connection on the IP address we get from a lookup derived().lowestLayer().async_connect( - results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, - derived().getptr()))); + results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, + derived().getptr()))); } - + /// @brief Write a request to the remote agent void request() { if (!m_request) return; - + // Set up an HTTP GET request message m_req.emplace(); m_req->version(11); @@ -243,50 +243,50 @@ namespace mtconnect::source::adapter::agent_adapter { m_req->set(http::field::host, m_url.getHost()); m_req->set(http::field::user_agent, "MTConnect Agent/2.0"); m_req->set(http::field::connection, "keep-alive"); - + if (m_closeConnectionAfterResponse) { m_req->set(http::field::connection, "close"); } - + derived().lowestLayer().expires_after(m_timeout); - + LOG(debug) << "Agent adapter making request: " << m_url.getUrlText(nullopt) << " target " - << m_request->getTarget(m_url); - + << m_request->getTarget(m_url); + http::async_write(derived().stream(), *m_req, beast::bind_front_handler(&SessionImpl::onWrite, derived().getptr())); } - + /// @brief Callback on successful write to the agent /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onWrite(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Cannot send request: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + if (!m_request) { LOG(error) << "Wrote but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + derived().lowestLayer().expires_after(m_timeout); - + // Receive the HTTP response m_headerParser.emplace(); http::async_read_header( - derived().stream(), m_buffer, *m_headerParser, - asio::bind_executor(m_strand, - beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); + derived().stream(), m_buffer, *m_headerParser, + asio::bind_executor(m_strand, + beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); } - + /// @brief Callback after write to process the message header /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred @@ -295,7 +295,7 @@ namespace mtconnect::source::adapter::agent_adapter { if (ec) { LOG(error) << "Agent Adapter Error getting request header: " << ec.category().name() << " " - << ec.message(); + << ec.message(); derived().lowestLayer().close(); if (m_request->m_stream && ec == beast::error::timeout) { @@ -306,16 +306,16 @@ namespace mtconnect::source::adapter::agent_adapter { { return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + return; } - + if (!m_request) { LOG(error) << "Received a header but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + auto &msg = m_headerParser->get(); if (msg.version() < 11) { @@ -326,7 +326,7 @@ namespace mtconnect::source::adapter::agent_adapter { { m_closeOnRead = a->value() == "close"; } - + if (m_request->m_stream && m_headerParser->chunked()) { onChunkedContent(); @@ -334,49 +334,49 @@ namespace mtconnect::source::adapter::agent_adapter { else { derived().lowestLayer().expires_after(m_timeout); - + m_textParser.emplace(std::move(*m_headerParser)); http::async_read(derived().stream(), m_buffer, *m_textParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } } - + /// @brief Callback after header processing to read the body of the response /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onRead(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Error getting response: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "read"); } - + if (!m_request) { LOG(error) << "read data but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + derived().lowestLayer().expires_after(m_timeout); - + if (!derived().lowestLayer().socket().is_open()) derived().disconnect(); - + processData(m_textParser->get().body()); - + m_textParser.reset(); m_req.reset(); - + auto next = m_request->m_next; m_request.reset(); - + if (m_closeOnRead) close(); - + if (next) { next(); @@ -388,10 +388,10 @@ namespace mtconnect::source::adapter::agent_adapter { makeRequest(req); } } - + /// @name Streaming related methods ///@{ - + /// @brief Find the x-multipart-replace MIME boundary /// @return the boundary string inline string findBoundary() @@ -415,10 +415,10 @@ namespace mtconnect::source::adapter::agent_adapter { } } } - + return ""; } - + /// @brief Create a function to handle the chunk header /// /// Sets the chunk parser on chunk header @@ -433,32 +433,32 @@ namespace mtconnect::source::adapter::agent_adapter { cout << "Ext: " << c.first << ": " << c.second << endl; #endif derived().lowestLayer().expires_after(m_timeout); - + if (ec) { return failed(ec, "Failed in chunked extension parse"); } }; - + m_chunkParser->on_chunk_header(m_chunkHeaderHandler); } - + /// @brief Parse the header and get the size and type /// @return `true` if successful bool parseMimeHeader() { using namespace boost; namespace algo = boost::algorithm; - + if (m_chunk.data().size() < 128) { LOG(trace) << "Not enough data for mime header: " << m_chunk.data().size(); return false; } - + auto start = static_cast(m_chunk.data().data()); boost::string_view view(start, m_chunk.data().size()); - + auto bp = view.find(m_boundary.c_str()); if (bp == boost::string_view::npos) { @@ -468,7 +468,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + auto ep = view.find("\r\n\r\n", bp); if (bp == boost::string_view::npos) { @@ -479,11 +479,11 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } ep += 4; - + using string_view_range = boost::iterator_range; auto svi = string_view_range(view.begin() + bp, view.end()); auto lp = boost::ifind_first(svi, boost::string_view("content-length:")); - + if (lp.empty()) { LOG(warning) << "Cannot find the content-length"; @@ -492,7 +492,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + boost::string_view length(lp.end()); auto digits = length.substr(0, length.find("\n")); auto finder = boost::token_finder(algo::is_digit(), algo::token_compress_on); @@ -505,14 +505,14 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + m_chunkLength = boost::lexical_cast(rng); m_hasHeader = true; m_chunk.consume(ep); - + return true; } - + /// @brief Creates the function to handle chunk body /// /// Sets the chunk parse on chunk body. @@ -527,15 +527,15 @@ namespace mtconnect::source::adapter::agent_adapter { "Stream body but no request"); return body.size(); } - + { std::ostream cstr(&m_chunk); cstr << body; } - + LOG(trace) << "Received: -------- " << m_chunk.size() << " " << remain << "\n" - << body << "\n-------------"; - + << body << "\n-------------"; + if (!m_hasHeader) { if (!parseMimeHeader()) @@ -544,27 +544,27 @@ namespace mtconnect::source::adapter::agent_adapter { return body.size(); } } - + auto len = m_chunk.size(); if (len >= m_chunkLength) { auto start = static_cast(m_chunk.data().data()); string_view sbuf(start, m_chunkLength); - + LOG(trace) << "Received Chunk: --------\n" << sbuf << "\n-------------"; - + processData(string(sbuf)); - + m_chunk.consume(m_chunkLength); m_hasHeader = false; } - + return body.size(); }; - + m_chunkParser->on_chunk_body(m_chunkHandler); } - + /// @brief Begins the async chunk reading if the boundry is found void onChunkedContent() { @@ -575,48 +575,48 @@ namespace mtconnect::source::adapter::agent_adapter { failed(ec, "Cannot find boundary"); return; } - + LOG(trace) << "Found boundary: " << m_boundary; - + m_chunkParser.emplace(std::move(*m_headerParser)); createChunkHeaderHandler(); createChunkBodyHandler(); - + derived().lowestLayer().expires_after(m_timeout); - + http::async_read(derived().stream(), m_buffer, *m_chunkParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } - + protected: tcp::resolver m_resolver; std::optional m_resolution; beast::flat_buffer m_buffer; // (Must persist between reads) std::optional> m_req; - + // Chunked content handling. std::optional> m_headerParser; std::optional> m_chunkParser; std::optional> m_textParser; asio::io_context::strand m_strand; url::Url m_url; - + std::function - m_chunkHandler; + m_chunkHandler; std::function - m_chunkHeaderHandler; - + m_chunkHeaderHandler; + std::string m_boundary; std::string m_contentType; - + size_t m_chunkLength; bool m_hasHeader = false; boost::asio::streambuf m_chunk; - + // For request queuing std::optional m_request; RequestQueue m_queue; }; - + } // namespace mtconnect::source::adapter::agent_adapter diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index 964b0de52..305bab57a 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -197,7 +197,7 @@ namespace mtconnect { { stringstream msg; msg << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " - "' or Topics block"; + "' or Topics block"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } diff --git a/src/mtconnect/source/adapter/shdr/connector.hpp b/src/mtconnect/source/adapter/shdr/connector.hpp index ad0e9ed14..66d4ff3c4 100644 --- a/src/mtconnect/source/adapter/shdr/connector.hpp +++ b/src/mtconnect/source/adapter/shdr/connector.hpp @@ -105,7 +105,7 @@ namespace mtconnect::source::adapter::shdr { void resolved(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results); void connected(const boost::system::error_code &error, - const boost::asio::ip::tcp::endpoint& endpoint); + const boost::asio::ip::tcp::endpoint &endpoint); void writer(boost::system::error_code ec, std::size_t length); void reader(boost::system::error_code ec, std::size_t length); bool parseSocketBuffer(); diff --git a/src/mtconnect/source/source.cpp b/src/mtconnect/source/source.cpp index d67880b97..97d5ebb83 100644 --- a/src/mtconnect/source/source.cpp +++ b/src/mtconnect/source/source.cpp @@ -15,12 +15,12 @@ // limitations under the License. // -#include - #include "mtconnect/source/source.hpp" #include +#include + #include "mtconnect/logging.hpp" namespace mtconnect::source { @@ -45,18 +45,18 @@ namespace mtconnect::source { std::string CreateIdentityHash(const std::string &input) { using namespace std; - + boost::uuids::detail::sha1 sha1; sha1.process_bytes(input.c_str(), input.length()); boost::uuids::detail::sha1::digest_type digest; sha1.get_digest(digest); - + ostringstream identity; identity << '_' << std::hex; for (int i = 0; i < 5; i++) - identity << (uint16_t) digest[i]; + identity << (uint16_t)digest[i]; return identity.str(); } - + } // namespace mtconnect::source diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp index 8ff888bb4..cdef79b11 100644 --- a/src/mtconnect/source/source.hpp +++ b/src/mtconnect/source/source.hpp @@ -95,7 +95,7 @@ namespace mtconnect { std::string m_name; boost::asio::io_context::strand m_strand; }; - + /// @brief create a unique identity hash for an XML id starting with an `_` and 10 hex digits /// @param text the text to create the hashed id /// @returns a string with the hashed result diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 6187923b7..8ccbeb09e 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -66,9 +66,10 @@ namespace boost::asio { namespace mtconnect { // Message for when enumerations do not exist in an array/enumeration const int ENUM_MISS = -1; - + /// @brtief Fatal Error Exception - /// An exception that get thrown to shut down the application. Only caught by the top level worker thread. + /// An exception that get thrown to shut down the application. Only caught by the top level worker + /// thread. class FatalException : public std::exception { public: @@ -83,7 +84,7 @@ namespace mtconnect { FatalException(const char *str) : m_what(str) {} /// @brief Create a default fatal exception /// Has the message `Fatal Exception Occurred` - FatalException() : m_what("Fatal Exception Occurred") {} + FatalException() : m_what("Fatal Exception Occurred") {} /// @brief Copy construction from an exception /// @param ex the exception FatalException(const std::exception &ex) : m_what(ex.what()) {} @@ -91,17 +92,15 @@ namespace mtconnect { FatalException(const FatalException &) = default; /// @brief Default destructor ~FatalException() = default; - + /// @brief gets the message /// @returns the message as a string - const char* what() const noexcept override { return m_what.c_str(); } - + const char *what() const noexcept override { return m_what.c_str(); } + protected: std::string m_what; - }; - - + /// @brief Time formats enum TimeFormat { @@ -110,7 +109,7 @@ namespace mtconnect { GMT_UV_SEC, ///< GMT with microsecond resolution LOCAL ///< Time using local time zone }; - + /// @brief Converts string to floating point numberss /// @param[in] text the number /// @return the converted value or 0.0 if incorrect. @@ -131,7 +130,7 @@ namespace mtconnect { } return value; } - + /// @brief Converts string to integer /// @param[in] text the number /// @return the converted value or 0 if incorrect. @@ -152,7 +151,7 @@ namespace mtconnect { } return value; } - + /// @brief converts a double to a string /// @param[in] value the double /// @return the string representation of the double (10 places max) @@ -163,18 +162,18 @@ namespace mtconnect { s << std::setprecision(precision) << value; return s.str(); } - + /// @brief inline formattor support for doubles class format_double_stream { protected: double val; - + public: /// @brief create a formatter /// @param[in] v the value format_double_stream(double v) { val = v; } - + /// @brief writes a double to an output stream with up to 10 digits of precision /// @tparam _CharT from std::basic_ostream /// @tparam _Traits from std::basic_ostream @@ -183,19 +182,19 @@ namespace mtconnect { /// @return reference to the output stream template inline friend std::basic_ostream<_CharT, _Traits> &operator<<( - std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) + std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) { constexpr int precision = std::numeric_limits::digits10; os << std::setprecision(precision) << fmter.val; return os; } }; - + /// @brief create a `format_doulble_stream` /// @param[in] v the value /// @return the format_double_stream inline format_double_stream formatted(double v) { return format_double_stream(v); } - + /// @brief Convert text to upper case /// @param[in,out] text text /// @return upper-case of text as string @@ -203,10 +202,10 @@ namespace mtconnect { { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::toupper(c); }); - + return text; } - + /// @brief Simple check if a number as a string is negative /// @param s the numbeer /// @return `true` if positive @@ -217,10 +216,10 @@ namespace mtconnect { if (!isdigit(c)) return false; } - + return true; } - + /// @brief Checks if a string is a valid integer /// @param s the string /// @return `true` if is `[+-]\d+` @@ -229,21 +228,21 @@ namespace mtconnect { auto iter = s.cbegin(); if (*iter == '-' || *iter == '+') ++iter; - + for (; iter != s.end(); iter++) { if (!isdigit(*iter)) return false; } - + return true; } - + /// @brief Gets the local time /// @param[in] time the time /// @param[out] buf struct tm AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -259,7 +258,7 @@ namespace mtconnect { #else namespace tzchrono = date; #endif - + switch (format) { case HUM_READ: @@ -275,10 +274,10 @@ namespace mtconnect { return date::format("%Y-%m-%dT%H:%M:%S%z", zt); } } - + return ""; } - + /// @brief get the current time in the given format /// /// cover method for `getCurrentTime()` with `system_clock::now()` @@ -289,7 +288,7 @@ namespace mtconnect { { return getCurrentTime(std::chrono::system_clock::now(), format); } - + /// @brief Get the current time as a unsigned uns64 since epoch /// @tparam timePeriod the resolution type of time /// @return the time as an uns64 @@ -297,18 +296,18 @@ namespace mtconnect { inline uint64_t getCurrentTimeIn() { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } - + /// @brief Current time in microseconds since epoch /// @return the time as uns64 in microsecnods inline uint64_t getCurrentTimeInMicros() { return getCurrentTimeIn(); } - + /// @brief Current time in seconds since epoch /// @return the time as uns64 in seconds inline uint64_t getCurrentTimeInSec() { return getCurrentTimeIn(); } - + /// @brief Parse the given time /// @param aTime the time in text /// @return uns64 in microseconds since epoch @@ -328,12 +327,12 @@ namespace mtconnect { date::from_stream(str, "%FT%T%Z", fields, &abbrev, &offset); if (!fields.ymd.ok() || !fields.tod.in_conventional_range()) return 0; - + micros microdays {date::sys_days(fields.ymd)}; auto us = fields.tod.to_duration().count() + microdays.time_since_epoch().count(); return us; } - + /// @brief escaped reserved XML characters from text /// @param data text with reserved characters escaped inline void replaceIllegalCharacters(std::string &data) @@ -341,30 +340,30 @@ namespace mtconnect { for (auto i = 0u; i < data.length(); i++) { char c = data[i]; - + switch (c) { case '&': data.replace(i, 1, "&"); break; - + case '<': data.replace(i, 1, "<"); break; - + case '>': data.replace(i, 1, ">"); break; } } } - + /// @brief add namespace prefixes to each element of the XPath /// @param[in] aPath the path to modify /// @param[in] aPrefix the prefix to add /// @return the modified path prefixed AGENT_LIB_API std::string addNamespace(const std::string aPath, const std::string aPrefix); - + /// @brief removes white space at the beginning of a string /// @param[in,out] s the string /// @return string with spaces removed @@ -373,7 +372,7 @@ namespace mtconnect { boost::algorithm::trim_left(s); return s; } - + /// @brief removes whitespace from the end of the string /// @param[in,out] s the string /// @return string with spaces removed @@ -382,7 +381,7 @@ namespace mtconnect { boost::algorithm::trim_right(s); return s; } - + /// @brief removes spaces from the beginning and end of a string /// @param[in] s the string /// @return string with spaces removed @@ -391,7 +390,7 @@ namespace mtconnect { boost::algorithm::trim(s); return s; } - + /// @brief split a string into two parts using a ':' separator /// @param key the key to split /// @return a pair of the key and an optional prefix. @@ -403,7 +402,7 @@ namespace mtconnect { else return {key, std::nullopt}; } - + /// @brief Case insensitive equals /// @param a first string /// @param b second string @@ -412,14 +411,14 @@ namespace mtconnect { { if (a.size() != b.size()) return false; - + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); + return tolower(a) == tolower(b); + }); } - + using Attributes = std::map; - + /// @brief overloaded pattern for variant visitors using list of lambdas /// @tparam ...Ts list of lambda classes template @@ -429,7 +428,7 @@ namespace mtconnect { }; template overloaded(Ts...) -> overloaded; - + /// @brief Reverse an iterable /// @tparam T The iterable type template @@ -437,13 +436,13 @@ namespace mtconnect { { private: T &m_iterable; - + public: explicit reverse(T &iterable) : m_iterable(iterable) {} auto begin() const { return std::rbegin(m_iterable); } auto end() const { return std::rend(m_iterable); } }; - + /// @brief observation sequence type using SequenceNumber_t = uint64_t; /// @brief set of data item ids for filtering @@ -455,16 +454,16 @@ namespace mtconnect { using Timestamp = std::chrono::time_point; using Timestamp = std::chrono::time_point; using StringList = std::list; - + /// @name Configuration related methods ///@{ - + /// @brief Variant for configuration options using ConfigOption = std::variant; + Milliseconds, StringList>; /// @brief A map of name to option value using ConfigOptions = std::map; - + /// @brief Get an option if available /// @tparam T the option type /// @param options the set of options @@ -479,7 +478,7 @@ namespace mtconnect { else return std::nullopt; } - + /// @brief checks if a boolean option is set /// @param options the set of options /// @param name the name of the option @@ -492,7 +491,7 @@ namespace mtconnect { else return false; } - + /// @brief checks if there is an option /// @param[in] options the set of options /// @param[in] name the name of the option @@ -502,7 +501,7 @@ namespace mtconnect { auto v = options.find(name); return v != options.end(); } - + /// @brief convert an option from a string to a typed option /// @param[in] s the /// @param[in] def template for the option @@ -515,29 +514,29 @@ namespace mtconnect { { std::string sv = std::get(option); visit(overloaded {[&option, &sv](const std::string &) { - if (sv.empty()) - option = std::monostate(); - else - option = sv; - }, - [&option, &sv](const int &) { option = stoi(sv); }, - [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, - [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, - [&option, &sv](const double &) { option = stod(sv); }, - [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, - [&option, &sv](const StringList &) { - StringList list; - boost::split(list, sv, boost::is_any_of(",")); - for (auto &s : list) - boost::trim(s); - option = list; - }, - [](const auto &) {}}, + if (sv.empty()) + option = std::monostate(); + else + option = sv; + }, + [&option, &sv](const int &) { option = stoi(sv); }, + [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, + [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, + [&option, &sv](const double &) { option = stod(sv); }, + [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, + [&option, &sv](const StringList &) { + StringList list; + boost::split(list, sv, boost::is_any_of(",")); + for (auto &s : list) + boost::trim(s); + option = list; + }, + [](const auto &) {}}, def); } return option; } - + /// @brief convert from a string option to a size /// /// Recognizes the following suffixes: @@ -555,7 +554,7 @@ namespace mtconnect { using namespace std; using boost::regex; using boost::smatch; - + auto value = GetOption(options, name); if (value) { @@ -572,11 +571,11 @@ namespace mtconnect { case 'G': case 'g': size *= 1024; - + case 'M': case 'm': size *= 1024; - + case 'K': case 'k': size *= 1024; @@ -590,10 +589,10 @@ namespace mtconnect { throw std::runtime_error(msg.str()); } } - + return size; } - + /// @brief adds a property tree node to an option set /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the options set @@ -619,7 +618,7 @@ namespace mtconnect { } } } - + /// @brief adds a property tree node to an option set with defaults /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the option set @@ -647,7 +646,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief combine two option sets /// @param[in,out] options existing set of options /// @param[in] entries options to add or update @@ -658,7 +657,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief get options from a property tree and create typed options /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options option set to modify @@ -676,9 +675,9 @@ namespace mtconnect { } AddOptions(tree, options, entries); } - + /// @} - + /// @brief Format a timestamp as a string in microseconds /// @param[in] ts the timestamp /// @return the time with microsecond resolution @@ -696,7 +695,7 @@ namespace mtconnect { time.append("Z"); return time; } - + /// @brief Capitalize a word /// /// Has special treatment of acronyms like AC, DC, PH, etc. @@ -707,12 +706,12 @@ namespace mtconnect { std::string::const_iterator end) { using namespace std; - + // Exceptions to the rule const static std::unordered_map exceptions = { - {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, - {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; - + {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, + {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; + std::string_view s(&*start, distance(start, end)); const auto &w = exceptions.find(s); ostream_iterator out(camel); @@ -726,7 +725,7 @@ namespace mtconnect { transform(start + 1, end, out, ::tolower); } } - + /// @brief creates an upper-camel-case string from words separated by an underscore (`_`) with /// optional prefix /// @@ -740,20 +739,20 @@ namespace mtconnect { using namespace std; if (type.empty()) return ""; - + ostringstream camel; - + auto start = type.begin(); decltype(start) end; - + auto colon = type.find(':'); - + if (colon != string::npos) { prefix = type.substr(0ul, colon); start += colon + 1; } - + bool done; do { @@ -766,10 +765,10 @@ namespace mtconnect { start = end + 1; } } while (!done); - + return camel.str(); } - + /// @brief parse a string timestamp to a `Timestamp` /// @param timestamp[in] the timestamp as a string /// @return converted `Timestamp` @@ -784,22 +783,22 @@ namespace mtconnect { } return ts; } - + /// @brief Creates a comparable schema version from a major and minor number #define SCHEMA_VERSION(major, minor) (major * 100 + minor) - + /// @brief Get the default schema version of the agent as a string /// @return the version inline std::string StrDefaultSchemaVersion() { return std::to_string(AGENT_VERSION_MAJOR) + "." + std::to_string(AGENT_VERSION_MINOR); } - + inline constexpr int32_t IntDefaultSchemaVersion() { return SCHEMA_VERSION(AGENT_VERSION_MAJOR, AGENT_VERSION_MINOR); } - + /// @brief convert a string version to a major and minor as two integers separated by a char. /// @param s the version inline int32_t IntSchemaVersion(const std::string &s) @@ -817,12 +816,12 @@ namespace mtconnect { return SCHEMA_VERSION(major, minor); } } - + /// @brief Retrieve the best Host IP address from the network interfaces. /// @param[in] context the boost asio io_context for resolving the address /// @param[in] onlyV4 only consider IPV4 addresses if `true` std::string GetBestHostAddress(boost::asio::io_context &context, bool onlyV4 = false); - + /// @brief Function to create a unique id given a sha1 namespace and an id. /// /// Creates a base 64 encoded version of the string and removes any illegal characters @@ -832,31 +831,32 @@ namespace mtconnect { /// @param[in] sha the sha1 namespace to use as context /// @param[in] id the id to use transform /// @returns Returns the first 16 characters of the base 64 encoded sha1 - inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, const std::string &id) + inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, + const std::string &id) { using namespace std; using namespace boost::uuids::detail; - + sha1 sha(contextSha); - + constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"); constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; }; constexpr auto isIDChar = [isIDStartChar](unsigned char c) -> bool { return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-'; }; - + sha.process_bytes(id.data(), id.length()); - sha1::digest_type digest; + sha1::digest_type digest; sha.get_digest(digest); - - auto data = (unsigned int *) digest; - + + auto data = (unsigned int *)digest; + string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); - + s.erase(len - 1); s.erase(std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar)), s.end()); - + // Check if the character is legal. if (!isIDStartChar(s[0])) { @@ -865,39 +865,39 @@ namespace mtconnect { s.erase(0, 1); s[0] = startc[c % startc.size()]; } - + s.erase(16); - + return s; } - + namespace url { using UrlQueryPair = std::pair; - + /// @brief A map of URL query parameters that can format as a string struct AGENT_LIB_API UrlQuery : public std::map { using std::map::map; - + /// @brief join the parameters as `=&=&...` /// @return std::string join() const { std::stringstream ss; bool has_pre = false; - + for (const auto &kv : *this) { if (has_pre) ss << '&'; - + ss << kv.first << '=' << kv.second; has_pre = true; } - + return ss.str(); } - + /// @brief Merge twos sets over-writing existing pairs set with `query` and adding new pairs /// @param query query to merge void merge(UrlQuery query) @@ -908,15 +908,15 @@ namespace mtconnect { } } }; - + /// @brief URL struct to parse and format URLs struct AGENT_LIB_API Url { /// @brief Variant for the Host that is either a host name or an ip address using Host = std::variant; - + std::string m_protocol; ///< either `http` or `https` - + Host m_host; ///< the host component std::optional m_username; ///< optional username std::optional m_password; ///< optional password @@ -924,22 +924,22 @@ namespace mtconnect { std::string m_path = "/"; ///< The path component UrlQuery m_query; ///< Query parameters std::string m_fragment; ///< The component after a `#` - + /// @brief Visitor to format the Host as a string struct HostVisitor { std::string operator()(std::string v) const { return v; } - + std::string operator()(boost::asio::ip::address v) const { return v.to_string(); } }; - + /// @brief Get the host as a string /// @return the host std::string getHost() const { return std::visit(HostVisitor(), m_host); } /// @brief Get the port as a string /// @return the port std::string getService() const { return boost::lexical_cast(getPort()); } - + /// @brief Get the path and the query portion of the URL /// @return the path and query std::string getTarget() const @@ -949,7 +949,7 @@ namespace mtconnect { else return m_path; } - + /// @brief Format a target using the existing host and port to make a request /// @param device an optional device /// @param operation the operation (probe,sample,current, or asset) @@ -961,7 +961,7 @@ namespace mtconnect { UrlQuery uq {m_query}; if (!query.empty()) uq.merge(query); - + std::stringstream path; path << m_path; if (m_path[m_path.size() - 1] != '/') @@ -972,10 +972,10 @@ namespace mtconnect { path << operation; if (uq.size() > 0) path << '?' << uq.join(); - + return path.str(); } - + int getPort() const { if (m_port) @@ -987,7 +987,7 @@ namespace mtconnect { else return 0; } - + /// @brief Format the URL as text /// @param device optional device to add to the URL /// @return formatted URL @@ -999,7 +999,7 @@ namespace mtconnect { url << *device; return url.str(); } - + /// @brief parse a string to a Url /// @return parsed URL static Url parse(const std::string_view &url); diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 2a889c87a..41a60bf69 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -63,7 +63,7 @@ class AgentTest : public testing::Test public: typedef std::map map_type; using queue_type = list; - + protected: void SetUp() override { @@ -71,19 +71,19 @@ class AgentTest : public testing::Test m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 25, true); m_agentId = to_string(getCurrentTimeInSec()); } - + void TearDown() override { m_agentTestHelper.reset(); } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 7878, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + public: std::string m_agentId; std::unique_ptr m_agentTestHelper; - + std::chrono::milliseconds m_delay {}; }; @@ -91,18 +91,18 @@ TEST_F(AgentTest, Constructor) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.7"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/badPath.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - - ASSERT_THROW(agent->initialize(context), std::runtime_error); + + ASSERT_THROW(agent->initialize(context), FatalException); agent.reset(); - + agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/test_config.xml", options); - + context = std::make_shared(); context->m_contract = agent->makePipelineContract(); ASSERT_NO_THROW(agent->initialize(context)); @@ -114,17 +114,17 @@ TEST_F(AgentTest, Probe) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); @@ -135,13 +135,13 @@ TEST_F(AgentTest, FailWithDuplicateDeviceUUID) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.5"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/dup_uuid.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - - ASSERT_THROW(agent->initialize(context), std::runtime_error); + + ASSERT_THROW(agent->initialize(context), FatalException); } TEST_F(AgentTest, should_return_error_for_unknown_device) @@ -159,7 +159,7 @@ TEST_F(AgentTest, should_return_2_6_error_for_unknown_device) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/LinuxCN/probe"); string message = (string) "Could not find the device 'LinuxCN'"; @@ -180,7 +180,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -188,7 +188,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -203,7 +203,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { QueryMap query {{"path", "//////Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -212,7 +212,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//////Linear"); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -221,7 +221,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//Axes?//Linear"); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -243,12 +243,12 @@ TEST_F(AgentTest, should_handle_a_correct_path) "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:ComponentStream", 1); } - + { QueryMap query { - {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; + {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:SpindleSpeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:Load", "UNAVAILABLE"); @@ -266,7 +266,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -274,7 +274,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -288,10 +288,10 @@ TEST_F(AgentTest, should_report_an_invalid_uri) TEST_F(AgentTest, should_report_a_2_6_invalid_uri) { using namespace rest_sink; - + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/bad_path"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -301,25 +301,25 @@ TEST_F(AgentTest, should_report_a_2_6_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/bad/path/"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/LinuxCNC/current/blah"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } @@ -329,21 +329,21 @@ TEST_F(AgentTest, should_handle_current_at) { QueryMap query; PARSE_XML_RESPONSE_QUERY("/current", query); - + addAdapter(); - + // Get the current position auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); char line[80] = {0}; - + // Add many events for (int i = 1; i <= 100; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 0; i < 100; i++) { @@ -354,7 +354,7 @@ TEST_F(AgentTest, should_handle_current_at) // m_agentTestHelper->printsession(); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test buffer wrapping // Add a large many events for (int i = 101; i <= 301; i++) @@ -362,7 +362,7 @@ TEST_F(AgentTest, should_handle_current_at) sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 100; i < 301; i++) { @@ -371,7 +371,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Check the first couple of items in the list for (int j = 0; j < 10; j++) { @@ -381,7 +381,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test out of range... { auto i = circ.getSequence() - circ.getBufferSize() - seq - 1; @@ -397,25 +397,25 @@ TEST_F(AgentTest, should_handle_current_at) TEST_F(AgentTest, should_handle_64_bit_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Initialize the sliding buffer at a very large number. auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + uint64_t start = (((uint64_t)1) << 48) + 1317; circ.setSequence(start); - + // Add many events for (int i = 1; i <= 500; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (uint64_t i = start + 300; i < start + 500; i++) { @@ -429,22 +429,22 @@ TEST_F(AgentTest, should_handle_64_bit_current_at) TEST_F(AgentTest, should_report_out_of_range_for_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + { query["at"] = to_string(seq); sprintf(line, "'at' must be less than %d", int32_t(seq)); @@ -452,9 +452,9 @@ TEST_F(AgentTest, should_report_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", line); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than %d", int32_t(seq)); @@ -468,21 +468,21 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); auto max = seq - 1; @@ -500,9 +500,9 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than 0"); @@ -523,15 +523,15 @@ TEST_F(AgentTest, AddAdapter) { addAdapter(); } TEST_F(AgentTest, should_download_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->setMaxCachedFileSize(100 * 1024); rest->getFileCache()->registerFile( - uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); - + uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); + // Reqyest the file... PARSE_TEXT_RESPONSE(uri.c_str()); ASSERT_FALSE(m_agentTestHelper->session()->m_body.empty()); @@ -542,13 +542,13 @@ TEST_F(AgentTest, should_download_file) TEST_F(AgentTest, should_report_not_found_when_cannot_find_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "INVALID_URI"); @@ -561,15 +561,15 @@ TEST_F(AgentTest, should_report_2_6_not_found_when_cannot_find_file) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -583,20 +583,20 @@ TEST_F(AgentTest, should_include_composition_ids_in_observations) { auto agent = m_agentTestHelper->m_agent.get(); addAdapter(); - + DataItemPtr motor = agent->getDataItemForDevice("LinuxCNC", "zt1"); ASSERT_TRUE(motor); - + DataItemPtr amp = agent->getDataItemForDevice("LinuxCNC", "zt2"); ASSERT_TRUE(amp); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|zt1|100|zt2|200"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']", "200"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']@compositionId", "zmotor"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']@compositionId", @@ -616,7 +616,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) "query parameter 'count': cannot convert " "string 'NON_INTEGER' to integer"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -625,14 +625,14 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(-size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'count' must not be zero(0)"); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -641,7 +641,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -650,7 +650,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -665,7 +665,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); int size = circ.getBufferSize() + 1; { @@ -681,7 +681,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -698,7 +698,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -712,13 +712,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=500"); @@ -729,13 +729,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=9999999"); @@ -746,13 +746,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be greater than "); value += to_string(-size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=-9999999"); @@ -768,24 +768,24 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) TEST_F(AgentTest, should_process_addapter_data) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Alarm[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); - + "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -799,17 +799,17 @@ TEST_F(AgentTest, should_get_samples_using_next_sequence) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 300; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); { @@ -825,27 +825,27 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_count) addAdapter(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + { query["path"] = "//DataItem[@name='Xact']"; query["from"] = to_string(seq); query["count"] = "10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 20).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -859,29 +859,29 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_negative_count) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "-10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -895,30 +895,30 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines auto start = seq - 20; for (int j = 0; j < 10; j++) @@ -927,18 +927,18 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); query["from"] = to_string(seq - 10); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 5); - + // Make sure we got 10 lines auto start = seq - 10; for (int j = 0; j < 5; j++) @@ -947,7 +947,7 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + // TODO: Test negative conditions // count < 0 // to > nextSequence @@ -963,11 +963,11 @@ TEST_F(AgentTest, should_give_empty_stream_with_no_new_samples) ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable", nullptr); ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", - nullptr); + doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", + nullptr); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:RotaryMode", "SPINDLE"); } - + { auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); QueryMap query {{"from", to_string(circ.getSequence())}}; @@ -980,31 +980,31 @@ TEST_F(AgentTest, should_not_leak_observations_when_added_to_buffer) { auto agent = m_agentTestHelper->m_agent.get(); QueryMap query; - + string device("LinuxCNC"), key("badKey"), value("ON"); SequenceNumber_t seqNum {0}; auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto event1 = circ.getFromBuffer(seqNum); ASSERT_FALSE(event1); - + { query["from"] = to_string(circ.getSequence()); PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); } - + key = "power"; - + auto di2 = agent->getDataItemForDevice(device, key); seqNum = m_agentTestHelper->addToBuffer(di2, {{"VALUE", value}}, chrono::system_clock::now()); auto event2 = circ.getFromBuffer(seqNum); ASSERT_EQ(3, event2.use_count()); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState", "ON"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState[1]", "UNAVAILABLE"); @@ -1017,53 +1017,53 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) #ifndef WIN32 QueryMap query; addAdapter(); - + // Set the sequence number near MAX_UINT32 auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); circ.setSequence(0xFFFFFFA0); SequenceNumber_t seq = circ.getSequence(); ASSERT_EQ((int64_t)0xFFFFFFA0, seq); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + i).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); } - + { query["from"] = to_string(seq); query["count"] = "128"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); - + for (int j = 0; j <= i; j++) { sprintf(line, "//m:DeviceStream//m:Line[%d]@sequence", j + 1); ASSERT_XML_PATH_EQUAL(doc, line, to_string(seq + j).c_str()); } } - + for (int j = 0; j <= i; j++) { query["from"] = to_string(seq + j); query["count"] = "1"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + j).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + j + 1).c_str()); } } - + ASSERT_EQ(uint64_t(0xFFFFFFA0) + 128ul, circ.getSequence()); #endif } @@ -1071,22 +1071,22 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) TEST_F(AgentTest, should_not_allow_duplicates_values) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1098,20 +1098,20 @@ TEST_F(AgentTest, should_not_allow_duplicates_values) TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) { addAdapter({{configuration::FilterDuplicates, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); } - + m_agentTestHelper->m_adapter->disconnected(); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1119,11 +1119,11 @@ TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[4]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->connected(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1143,31 +1143,31 @@ TEST_F(AgentTest, should_handle_auto_available_if_adapter_option_is_set) auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); } - + agent->disconnected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[3]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); @@ -1183,66 +1183,66 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) auto agent = m_agentTestHelper->m_agent.get(); auto adapter = m_agentTestHelper->m_adapter; auto id = adapter->getIdentity(); - + auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); } - + agent->connected(id, devices, false); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->connected(id, devices, false); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 3); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 5); } } @@ -1250,18 +1250,18 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]@timestamp", "2021-02-01T12:00:00Z"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::IgnoreTimestamps, true}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1273,7 +1273,7 @@ TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) TEST_F(AgentTest, InitialTimeSeriesValues) { addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", @@ -1285,41 +1285,41 @@ TEST_F(AgentTest, should_support_dynamic_calibration_data) { addAdapter({{configuration::ConversionRequired, true}}); auto agent = m_agentTestHelper->getAgent(); - + // Add a 10.111000 seconds m_agentTestHelper->m_adapter->protocolCommand( - "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); + "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); auto di = agent->getDataItemForDevice("LinuxCNC", "Yact"); ASSERT_TRUE(di); - + // TODO: Fix conversions auto &conv1 = di->getConverter(); ASSERT_TRUE(conv1); ASSERT_EQ(0.01, conv1->factor()); ASSERT_EQ(200.0, conv1->offset()); - + di = agent->getDataItemForDevice("LinuxCNC", "Zact"); ASSERT_TRUE(di); - + auto &conv2 = di->getConverter(); ASSERT_TRUE(conv2); ASSERT_EQ(0.02, conv2->factor()); ASSERT_EQ(300.0, conv2->offset()); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|Yact|200|Zact|600"); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " - "5119 5119 5118 " - "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); - + "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " + "5119 5119 5118 " + "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='y1']", "4"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='z1']", "18"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", - "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " - "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); + doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", + "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " + "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); } } @@ -1327,32 +1327,32 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_1) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|100"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|103"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[3]", "106"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106|load|108|load|112"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1366,15 +1366,15 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1382,10 +1382,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:32.000666|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:36.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1395,10 +1395,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData( - "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); - + "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1409,9 +1409,9 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:47.50|pos|45|pos|50"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1431,24 +1431,24 @@ TEST_F(AgentTest, period_filter_should_work_with_ignore_timestamps) // Test period filter with ignore timestamps m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::IgnoreTimestamps, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:32.000666|load|103|pos|25"); this_thread::sleep_for(11s); m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:40.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1462,23 +1462,23 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) // Test period filter with relative time m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::RelativeTime, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("0|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("5000|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("11000|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1490,13 +1490,13 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) TEST_F(AgentTest, reset_triggered_should_work) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("TIME1|pcount|0"); m_agentTestHelper->m_adapter->processData("TIME2|pcount|1"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|2"); m_agentTestHelper->m_adapter->processData("TIME4|pcount|0:DAY"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|5"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount[1]", "UNAVAILABLE"); @@ -1513,58 +1513,58 @@ TEST_F(AgentTest, reset_triggered_should_work) TEST_F(AgentTest, should_honor_references_when_getting_current_or_sample) { using namespace device_model; - + m_agentTestHelper->createAgent("/samples/reference_example.xml"); addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + string id = "mf"; auto item = agent->getDataItemForDevice((string) "LinuxCNC", id); auto comp = item->getComponent(); - + auto references = comp->getList("References"); ASSERT_TRUE(references); ASSERT_EQ(3, references->size()); auto reference = references->begin(); - + EXPECT_EQ("DataItemRef", (*reference)->getName()); - + EXPECT_EQ("chuck", (*reference)->get("name")); EXPECT_EQ("c4", (*reference)->get("idRef")); - + auto ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; reference++; - + EXPECT_EQ("door", (*reference)->get("name")); EXPECT_EQ("d2", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; - + reference++; EXPECT_EQ("electric", (*reference)->get("name")); EXPECT_EQ("ele", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::COMPONENT, ref->getReferenceType()); ASSERT_TRUE(ref->getComponent().lock()) << "DataItem was not resolved"; - + // Additional data items should be included { QueryMap query {{"path", "//BarFeederInterface"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); + doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Door']//m:DoorState", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:ChuckState", @@ -1577,34 +1577,34 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) m_agentTestHelper->createAgent("/samples/discrete_example.xml"); addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto msg = agent->getDataItemForDevice("LinuxCNC", "message"); ASSERT_TRUE(msg); ASSERT_EQ(true, msg->isDiscreteRep()); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); @@ -1619,17 +1619,17 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) TEST_F(AgentTest, should_honor_upcase_values) { addAdapter({{configuration::FilterDuplicates, true}, {configuration::UpcaseDataItemValue, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "HELLO"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::UpcaseDataItemValue, false}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "Hello"); @@ -1642,7 +1642,7 @@ TEST_F(AgentTest, should_handle_condition_activation) auto agent = m_agentTestHelper->getAgent(); auto logic = agent->getDataItemForDevice("LinuxCNC", "lp"); ASSERT_TRUE(logic); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/current"); @@ -1652,29 +1652,29 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Unavailable[@dataItemId='lp']", 1); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||XXX"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - "XXX"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + "XXX"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1691,28 +1691,28 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1724,21 +1724,21 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@nativeSeverity", "ALARM_D"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1755,18 +1755,18 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[2]@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " - "set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " + "set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1783,35 +1783,35 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[1]@qualifier", "HIGH"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL|2218|||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" "m:Fault[1]@nativeCode", "4200"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } } @@ -1819,55 +1819,55 @@ TEST_F(AgentTest, should_handle_empty_entry_as_last_pair_from_adapter) { addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto program = agent->getDataItemForDevice("LinuxCNC", "program"); ASSERT_TRUE(program); - + auto tool_id = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(tool_id); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B|line|C"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|D|block||line|E"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "D"); @@ -1882,17 +1882,17 @@ TEST_F(AgentTest, should_handle_constant_values) auto agent = m_agentTestHelper->getAgent(); auto di = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(di); - + di->setConstantValue("UNAVAILABLE"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); - + "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); @@ -1906,14 +1906,14 @@ TEST_F(AgentTest, should_handle_constant_values) TEST_F(AgentTest, should_handle_bad_data_item_from_adapter) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|bad|ignore|dummy|1244|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1927,18 +1927,18 @@ TEST_F(AgentTest, adapter_should_receive_commands) { addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_ioContext.run_for(2000ms); - + m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -1946,19 +1946,19 @@ TEST_F(AgentTest, adapter_should_receive_commands) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + device->setPreserveUuid(true); m_agentTestHelper->m_adapter->parseBuffer("* uuid: XXXXXXX\n"); m_agentTestHelper->m_ioContext.run_for(1000ms); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); } - + auto &options = m_agentTestHelper->m_adapter->getOptions(); ASSERT_EQ("MK-1234", *GetOption(options, configuration::Device)); } @@ -1968,21 +1968,21 @@ TEST_F(AgentTest, adapter_should_not_process_uuid_command_with_preserve_uuid) auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.3", 4, false, true, {{configuration::PreserveUUID, true}}); addAdapter(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_TRUE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "000"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); - + "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Block", "G01X00"); @@ -1995,37 +1995,37 @@ TEST_F(AgentTest, adapter_should_receive_device_commands) { m_agentTestHelper->createAgent("/samples/two_devices.xml"); auto agent = m_agentTestHelper->getAgent(); - + auto device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); auto device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + addAdapter(); - + auto device = - GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); + GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(device1->getComponentName(), device); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-2\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device2->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: new-uuid\n"); - + device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + ASSERT_EQ("new-uuid", string(*device2->getUuid())); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-1\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device1->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); - + ASSERT_EQ("another-uuid", string(*device1->getUuid())); } @@ -2033,19 +2033,19 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) { m_agentTestHelper->createAgent("/samples/kinematics.xml", 8, 4, "1.7", 25); addAdapter(); - + auto printer = m_agentTestHelper->m_agent->getPrinter("xml"); ASSERT_FALSE(printer->getModelChangeTime().empty()); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectVersion", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->parseBuffer("* adapterVersion: 2.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectVersion: 1.7\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "2.10"); @@ -2054,24 +2054,24 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) printer->getModelChangeTime().c_str()); ; } - + // Test updating device change time string old = printer->getModelChangeTime(); m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); ASSERT_GT(printer->getModelChangeTime(), old); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@deviceModelChangeTime", printer->getModelChangeTime().c_str()); ; } - + // Test Case insensitivity - + m_agentTestHelper->m_adapter->parseBuffer("* adapterversion: 3.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectversion: 1.6\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "3.10"); @@ -2085,14 +2085,14 @@ TEST_F(AgentTest, should_handle_uuid_change) auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + addAdapter(); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -2100,11 +2100,11 @@ TEST_F(AgentTest, should_handle_uuid_change) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + auto *pipe = static_cast(m_agentTestHelper->m_adapter->getPipeline()); - + ASSERT_EQ("MK-1234", pipe->getDevice()); - + { // TODO: Fix and make sure dom is updated so this xpath will parse correctly. // PARSE_XML_RESPONSE("/current?path=//Device[@uuid=\"MK-1234\"]"); @@ -2120,7 +2120,7 @@ TEST_F(AgentTest, should_handle_uuid_change) TEST_F(AgentTest, interval_should_be_a_valid_integer_value) { QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2130,7 +2130,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) "query parameter 'interval': cannot " "convert string 'NON_INTEGER' to integer"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2138,7 +2138,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be greater than -1"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; @@ -2146,7 +2146,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be less than 2147483647"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2163,7 +2163,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2178,7 +2178,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2192,13 +2192,13 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?interval=2147483647"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", "'interval' must be less than 2147483647"); @@ -2207,7 +2207,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2231,18 +2231,18 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto rest = m_agentTestHelper->getRestService(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); rest->start(); - + // Start a thread... QueryMap query; query["interval"] = "50"; query["heartbeat"] = to_string(heartbeatFreq.count()); query["from"] = to_string(circ.getSequence()); - + // Heartbeat test. Heartbeat should be sent in 200ms. Give // 25ms range. { auto slop {35ms}; - + auto startTime = system_clock::now(); PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); while (m_agentTestHelper->m_session->m_chunkBody.empty() && @@ -2251,34 +2251,34 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto delta = system_clock::now() - startTime; cout << "Delta after heartbeat: " << delta.count() << endl; ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); EXPECT_GT((heartbeatFreq + slop), delta) - << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); + << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); EXPECT_LT(heartbeatFreq, delta) << "delta > hbf: " << delta.count(); - + m_agentTestHelper->m_session->closeStream(); } - + // Set some data and make sure we get data within 40ms. // Again, allow for some slop. { auto delay {40ms}; auto slop {35ms}; - + PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); m_agentTestHelper->m_ioContext.run_for(delay); - + auto startTime = system_clock::now(); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(5ms); auto delta = system_clock::now() - startTime; cout << "Delta after data: " << delta.count() << endl; - + ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); PARSE_XML_CHUNK(); - + auto deltaMS = duration_cast(delta); EXPECT_GT(slop, deltaMS) << "delta " << deltaMS.count() << " < delay " << slop.count(); } @@ -2290,9 +2290,9 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + /// - Set up streaming every 100ms with a 1000ms heartbeat std::map query; query["interval"] = "100"; @@ -2300,7 +2300,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) query["count"] = "10"; query["from"] = to_string(circ.getSequence()); query["path"] = "//DataItem[@name='line']"; - + /// - Test to make sure the signal will push the sequence number forward and capture /// the new data. { @@ -2312,7 +2312,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(200ms); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@sequence", seq.c_str()); } @@ -2324,9 +2324,9 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + // Start a thread... std::map query; query["interval"] = "100"; @@ -2334,14 +2334,14 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) query["count"] = "10"; query["from"] = to_string(circ.getSequence() + 5); query["path"] = "//DataItem[@name='line']"; - + // Test to make sure the signal will push the sequence number forward and capture // the new data. { PARSE_XML_RESPONSE_QUERY("/LinuxCNC/sample", query); auto seq = to_string(circ.getSequence() + 20ull); m_agentTestHelper->m_ioContext.run_for(200ms); - + ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); } } @@ -2356,18 +2356,18 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) TEST_F(AgentTest, should_allow_making_observations_via_http_put) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["line"] = "205"; queries["power"] = "ON"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@timestamp", "2021-02-01T12:00:00Z"); @@ -2380,17 +2380,17 @@ TEST_F(AgentTest, should_allow_making_observations_via_http_put) TEST_F(AgentTest, put_condition_should_parse_condition_data) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["lp"] = "FAULT|2001|1||SCANHISTORYRESET"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Fault@timestamp", "2021-02-01T12:00:00Z"); @@ -2403,7 +2403,7 @@ TEST_F(AgentTest, put_condition_should_parse_condition_data) TEST_F(AgentTest, shound_add_asset_count_when_20) { m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.0", 25); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); @@ -2423,7 +2423,7 @@ TEST_F(AgentTest, pre_start_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_TRUE(called); @@ -2439,7 +2439,7 @@ TEST_F(AgentTest, pre_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2452,7 +2452,7 @@ TEST_F(AgentTest, post_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2465,7 +2465,7 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_FALSE(called); @@ -2476,24 +2476,24 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) TEST_F(AgentTest, device_should_have_hash_for_2_2) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + auto hash = device->get("hash"); ASSERT_EQ(28, hash.length()); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@hash", hash.c_str()); } - + auto devices = m_agentTestHelper->getAgent()->getDevices(); auto di = devices.begin(); - + { PARSE_XML_RESPONSE("/Agent/sample"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[2]@hash", (*di++)->get("hash").c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[3]@hash", (*di)->get("hash").c_str()); } @@ -2502,19 +2502,19 @@ TEST_F(AgentTest, device_should_have_hash_for_2_2) TEST_F(AgentTest, should_not_add_spaces_to_output) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|program|"); m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData( - "2024-01-22T20:00:00Z|program| |block| "); - + "2024-01-22T20:00:00Z|program| |block| "); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); @@ -2531,7 +2531,7 @@ TEST_F(AgentTest, should_set_sender_from_config_in_XML_header) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); @@ -2547,12 +2547,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_validation_is_fa PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2568,12 +2568,12 @@ TEST_F(AgentTest, should_set_validation_flag_in_header_when_version_2_5_validati PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); @@ -2589,12 +2589,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2604,19 +2604,19 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ TEST_F(AgentTest, should_initialize_observaton_to_initial_value_when_available) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|avail|AVAILABLE"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "0"); diff --git a/test_package/http_server_test.cpp b/test_package/http_server_test.cpp index ff73298c3..81c0a1c56 100644 --- a/test_package/http_server_test.cpp +++ b/test_package/http_server_test.cpp @@ -243,8 +243,9 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), boost::asio::detached); while (!m_done && m_context.run_for(20ms) > 0) @@ -311,8 +312,7 @@ class HttpServerTest : public testing::Test asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), static_cast(m_server->getPort()), std::placeholders::_1), - boost::asio::detached); - + boost::asio::detached); while (!m_client->m_connected) m_context.run_one(); diff --git a/test_package/json_printer_probe_test.cpp b/test_package/json_printer_probe_test.cpp index 1c2a1cb65..2aae84e22 100644 --- a/test_package/json_printer_probe_test.cpp +++ b/test_package/json_printer_probe_test.cpp @@ -362,7 +362,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir2 = load.at("/Relationships/1"_json_pointer); ASSERT_TRUE(dir2.is_object()); ASSERT_EQ(string("LIMIT"), dir2.at("/SpecificationRelationship/type"_json_pointer).get()); - ASSERT_EQ(string("spec1"), dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); + ASSERT_EQ(string("spec1"), + dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); auto limits = linear.at("/DataItems/5/DataItem"_json_pointer); ASSERT_TRUE(load.is_object()); @@ -371,7 +372,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir3 = limits.at("/Relationships/0"_json_pointer); ASSERT_TRUE(dir3.is_object()); ASSERT_EQ(string("bob"), dir3.at("/DataItemRelationship/name"_json_pointer).get()); - ASSERT_EQ(string("OBSERVATION"), dir3.at("/DataItemRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("OBSERVATION"), + dir3.at("/DataItemRelationship/type"_json_pointer).get()); ASSERT_EQ(string("xlc"), dir3.at("/DataItemRelationship/idRef"_json_pointer).get()); } diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index 13a047719..262e25081 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -252,8 +252,10 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), boost::asio::detached); + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), + boost::asio::detached); while (!m_done && !m_failed && m_context.run_for(20ms) > 0) ; diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index 07044401f..91b7a0e0f 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -241,8 +241,9 @@ TEST_F(WebsocketsTest, should_make_simple_request) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), - "{\"id\":\"1\",\"request\":\"probe\"}"s, std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"id\":\"1\",\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -270,8 +271,9 @@ TEST_F(WebsocketsTest, should_return_error_when_there_is_no_id) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -362,8 +364,7 @@ TEST_F(WebsocketsTest, should_return_error_when_bad_json_is_sent) start(); startClient(); - asio::spawn(m_context, - std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), + asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -399,7 +400,7 @@ TEST_F(WebsocketsTest, should_return_multiple_errors_when_parameters_are_invalid std::bind(&Client::request, m_client.get(), R"DOC({"id": 3, "request": "sample", "interval": 99999999999,"to": -1 })DOC", std::placeholders::_1), - boost::asio::detached); + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); diff --git a/test_package/xml_parser_test.cpp b/test_package/xml_parser_test.cpp index 88e61545a..f2735bead 100644 --- a/test_package/xml_parser_test.cpp +++ b/test_package/xml_parser_test.cpp @@ -87,7 +87,7 @@ TEST_F(XmlParserTest, Constructor) std::unique_ptr printer(new printer::XmlPrinter()); m_xmlParser = new parser::XmlParser(); ASSERT_THROW(m_xmlParser->parseFile(TEST_RESOURCE_DIR "/samples/badPath.xml", printer.get()), - std::runtime_error); + FatalException); delete m_xmlParser; m_xmlParser = nullptr; m_xmlParser = new parser::XmlParser(); From 3b0881227d6892dfc3dbfdf8eb92d6b3c919fbbf Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 16:53:20 +0100 Subject: [PATCH 29/84] Version 2.6.0.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1220305c..c89d74038 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 3) +set(AGENT_VERSION_BUILD 4) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From 587310623df58fadc46b6e89b07e67e7461846e9 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 16:54:06 +0100 Subject: [PATCH 30/84] Version 2.6.0.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1220305c..c89d74038 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 3) +set(AGENT_VERSION_BUILD 4) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From aedfff3928fd928af6c5c7f8889b63b4fe1de763 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Mon, 3 Nov 2025 12:25:37 +0100 Subject: [PATCH 31/84] Fixed test issues --- src/mtconnect/configuration/agent_config.cpp | 3 +- src/mtconnect/entity/xml_printer.cpp | 2 +- src/mtconnect/mqtt/mqtt_server_impl.hpp | 4 +- src/mtconnect/pipeline/pipeline.hpp | 3 +- src/mtconnect/printer/xml_printer.cpp | 2 +- .../mqtt_entity_sink/mqtt_entity_sink.cpp | 2 +- .../mqtt_entity_sink/mqtt_entity_sink.hpp | 4 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 1 - .../adapter/agent_adapter/agent_adapter.cpp | 2 +- .../source/adapter/shdr/connector.hpp | 2 +- src/mtconnect/source/source.cpp | 12 +- src/mtconnect/source/source.hpp | 2 +- src/mtconnect/utilities.hpp | 11 +- test_package/agent_test_helper.hpp | 4 +- test_package/http_server_test.cpp | 8 +- test_package/json_printer_probe_test.cpp | 6 +- test_package/mqtt_entity_sink_test.cpp | 437 ++++++++++-------- test_package/tls_http_server_test.cpp | 6 +- test_package/websockets_test.cpp | 15 +- 19 files changed, 300 insertions(+), 226 deletions(-) diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 90941458a..2d7db5882 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -58,8 +58,8 @@ #include "mtconnect/configuration/config_options.hpp" #include "mtconnect/device_model/device.hpp" #include "mtconnect/printer/xml_printer.hpp" -#include "mtconnect/sink/mqtt_sink/mqtt_service.hpp" #include "mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp" +#include "mtconnect/sink/mqtt_sink/mqtt_service.hpp" #include "mtconnect/sink/rest_sink/rest_service.hpp" #include "mtconnect/source/adapter/agent_adapter/agent_adapter.hpp" #include "mtconnect/source/adapter/mqtt/mqtt_adapter.hpp" @@ -1254,4 +1254,3 @@ namespace mtconnect::configuration { return false; } } // namespace mtconnect::configuration - diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp index 3cfb0ccb4..66d461886 100644 --- a/src/mtconnect/entity/xml_printer.cpp +++ b/src/mtconnect/entity/xml_printer.cpp @@ -19,8 +19,8 @@ #include -#include #include +#include #include "mtconnect/logging.hpp" #include "mtconnect/printer/xml_printer_helper.hpp" diff --git a/src/mtconnect/mqtt/mqtt_server_impl.hpp b/src/mtconnect/mqtt/mqtt_server_impl.hpp index b962d14ab..0a147d9ac 100644 --- a/src/mtconnect/mqtt/mqtt_server_impl.hpp +++ b/src/mtconnect/mqtt/mqtt_server_impl.hpp @@ -24,9 +24,9 @@ #include #include +#include #include #include -#include #include "mqtt_server.hpp" #include "mtconnect/configuration/config_options.hpp" @@ -218,7 +218,7 @@ namespace mtconnect { LOG(debug) << "Server topic_name: " << topic_name; LOG(debug) << "Server contents: " << contents; - for (const auto& sub : m_subs) + for (const auto &sub : m_subs) { if (mqtt::broker::compare_topic_filter(sub.topic, topic_name)) { diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp index 8fa7b8594..a6ce14c67 100644 --- a/src/mtconnect/pipeline/pipeline.hpp +++ b/src/mtconnect/pipeline/pipeline.hpp @@ -17,9 +17,10 @@ #pragma once -#include #include +#include + #include "mtconnect/config.hpp" #include "pipeline_context.hpp" #include "pipeline_contract.hpp" diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 3ea09f277..06c0da048 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include "mtconnect/asset/asset.hpp" #include "mtconnect/asset/cutting_tool.hpp" diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp index c826d0871..6dbdd23fc 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.cpp @@ -555,5 +555,5 @@ namespace mtconnect { } } // namespace mqtt_entity_sink - } // namespace sink + } // namespace sink } // namespace mtconnect diff --git a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp index 76586a92f..db71cf0ad 100644 --- a/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp +++ b/src/mtconnect/sink/mqtt_entity_sink/mqtt_entity_sink.hpp @@ -117,7 +117,7 @@ namespace mtconnect { std::string formatTimestamp(const Timestamp& timestamp); protected: - static constexpr size_t MAX_QUEUE_SIZE = 10000; // Maximum queued observations + static constexpr size_t MAX_QUEUE_SIZE = 10000; // Maximum queued observations std::string m_observationTopicPrefix; //! Observation topic prefix std::string m_deviceTopicPrefix; //! Device topic prefix @@ -134,5 +134,5 @@ namespace mtconnect { std::mutex m_queueMutex; }; } // namespace mqtt_entity_sink - } // namespace sink + } // namespace sink } // namespace mtconnect diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 46775087c..fe19d8436 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1616,4 +1616,3 @@ namespace mtconnect { } // namespace sink::rest_sink } // namespace mtconnect - diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index b99bff088..255c44872 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -135,7 +135,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_name = m_url.getUrlText(m_sourceDevice); m_identity = CreateIdentityHash(m_name); - + m_options.insert_or_assign(configuration::AdapterIdentity, m_identity); m_feedbackId = "XmlTransformFeedback:" + m_identity; diff --git a/src/mtconnect/source/adapter/shdr/connector.hpp b/src/mtconnect/source/adapter/shdr/connector.hpp index ad0e9ed14..66d4ff3c4 100644 --- a/src/mtconnect/source/adapter/shdr/connector.hpp +++ b/src/mtconnect/source/adapter/shdr/connector.hpp @@ -105,7 +105,7 @@ namespace mtconnect::source::adapter::shdr { void resolved(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results); void connected(const boost::system::error_code &error, - const boost::asio::ip::tcp::endpoint& endpoint); + const boost::asio::ip::tcp::endpoint &endpoint); void writer(boost::system::error_code ec, std::size_t length); void reader(boost::system::error_code ec, std::size_t length); bool parseSocketBuffer(); diff --git a/src/mtconnect/source/source.cpp b/src/mtconnect/source/source.cpp index d67880b97..97d5ebb83 100644 --- a/src/mtconnect/source/source.cpp +++ b/src/mtconnect/source/source.cpp @@ -15,12 +15,12 @@ // limitations under the License. // -#include - #include "mtconnect/source/source.hpp" #include +#include + #include "mtconnect/logging.hpp" namespace mtconnect::source { @@ -45,18 +45,18 @@ namespace mtconnect::source { std::string CreateIdentityHash(const std::string &input) { using namespace std; - + boost::uuids::detail::sha1 sha1; sha1.process_bytes(input.c_str(), input.length()); boost::uuids::detail::sha1::digest_type digest; sha1.get_digest(digest); - + ostringstream identity; identity << '_' << std::hex; for (int i = 0; i < 5; i++) - identity << (uint16_t) digest[i]; + identity << (uint16_t)digest[i]; return identity.str(); } - + } // namespace mtconnect::source diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp index 8ff888bb4..cdef79b11 100644 --- a/src/mtconnect/source/source.hpp +++ b/src/mtconnect/source/source.hpp @@ -95,7 +95,7 @@ namespace mtconnect { std::string m_name; boost::asio::io_context::strand m_strand; }; - + /// @brief create a unique identity hash for an XML id starting with an `_` and 10 hex digits /// @param text the text to create the hashed id /// @returns a string with the hashed result diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 70bddd58f..c303caffc 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -224,7 +224,7 @@ namespace mtconnect { #else namespace tzchrono = date; #endif - + switch (format) { case HUM_READ: @@ -819,7 +819,8 @@ namespace mtconnect { /// @param[in] sha the sha1 namespace to use as context /// @param[in] id the id to use transform /// @returns Returns the first 16 characters of the base 64 encoded sha1 - inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, const std::string &id) + inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, + const std::string &id) { using namespace std; using namespace boost::uuids::detail; @@ -833,11 +834,11 @@ namespace mtconnect { }; sha.process_bytes(id.data(), id.length()); - sha1::digest_type digest; + sha1::digest_type digest; sha.get_digest(digest); - auto data = (unsigned int *) digest; - + auto data = (unsigned int *)digest; + string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); diff --git a/test_package/agent_test_helper.hpp b/test_package/agent_test_helper.hpp index fcbeb0402..f1aa59a7d 100644 --- a/test_package/agent_test_helper.hpp +++ b/test_package/agent_test_helper.hpp @@ -125,6 +125,7 @@ class AgentTestHelper ~AgentTestHelper() { m_mqttService.reset(); + m_mqttEntitySink.reset(); m_restService.reset(); m_adapter.reset(); if (m_agent) @@ -323,8 +324,7 @@ class AgentTestHelper std::shared_ptr m_context; std::shared_ptr m_adapter; std::shared_ptr m_mqttService; - std::shared_ptr - m_mqttEntitySink; + std::shared_ptr m_mqttEntitySink; std::shared_ptr m_restService; std::shared_ptr m_loopback; diff --git a/test_package/http_server_test.cpp b/test_package/http_server_test.cpp index ff73298c3..81c0a1c56 100644 --- a/test_package/http_server_test.cpp +++ b/test_package/http_server_test.cpp @@ -243,8 +243,9 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), boost::asio::detached); while (!m_done && m_context.run_for(20ms) > 0) @@ -311,8 +312,7 @@ class HttpServerTest : public testing::Test asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), static_cast(m_server->getPort()), std::placeholders::_1), - boost::asio::detached); - + boost::asio::detached); while (!m_client->m_connected) m_context.run_one(); diff --git a/test_package/json_printer_probe_test.cpp b/test_package/json_printer_probe_test.cpp index 1c2a1cb65..2aae84e22 100644 --- a/test_package/json_printer_probe_test.cpp +++ b/test_package/json_printer_probe_test.cpp @@ -362,7 +362,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir2 = load.at("/Relationships/1"_json_pointer); ASSERT_TRUE(dir2.is_object()); ASSERT_EQ(string("LIMIT"), dir2.at("/SpecificationRelationship/type"_json_pointer).get()); - ASSERT_EQ(string("spec1"), dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); + ASSERT_EQ(string("spec1"), + dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); auto limits = linear.at("/DataItems/5/DataItem"_json_pointer); ASSERT_TRUE(load.is_object()); @@ -371,7 +372,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir3 = limits.at("/Relationships/0"_json_pointer); ASSERT_TRUE(dir3.is_object()); ASSERT_EQ(string("bob"), dir3.at("/DataItemRelationship/name"_json_pointer).get()); - ASSERT_EQ(string("OBSERVATION"), dir3.at("/DataItemRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("OBSERVATION"), + dir3.at("/DataItemRelationship/type"_json_pointer).get()); ASSERT_EQ(string("xlc"), dir3.at("/DataItemRelationship/idRef"_json_pointer).get()); } diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index 7ea003c7a..bb868908e 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -60,142 +60,181 @@ class MqttEntitySinkTest : public testing::Test std::shared_ptr m_client; std::unique_ptr m_jsonPrinter; uint16_t m_port {0}; - - void SetUp() override { + + void SetUp() override + { m_agentTestHelper = std::make_unique(); m_jsonPrinter = std::make_unique(2, true); } + + void TearDown() override + { + stopAgent(); + stopClient(); + stopServer(); - void TearDown() override { - if (m_client) { - m_client->stop(); - m_agentTestHelper->m_ioContext.run_for(500ms); - m_client.reset(); - } - if (m_server) { - m_server->stop(); - m_agentTestHelper->m_ioContext.run_for(500ms); - m_server.reset(); - } m_agentTestHelper.reset(); m_jsonPrinter.reset(); } - - void createAgent(std::string testFile = {}, ConfigOptions options = {}) { + + void createAgent(std::string testFile = {}, ConfigOptions options = {}) + { if (testFile == "") testFile = "/samples/test_config.xml"; ConfigOptions opts(options); MergeOptions( - opts, - { - {"MqttEntitySink", true}, - {configuration::MqttPort, m_port}, - {MqttCurrentInterval, 200ms}, - {MqttSampleInterval, 100ms}, - {configuration::MqttHost, "127.0.0.1"s}, - {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, - {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, - {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, - {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, - }); + opts, + { + {"MqttEntitySink", true}, + {configuration::MqttPort, m_port}, + {MqttCurrentInterval, 200ms}, + {MqttSampleInterval, 100ms}, + {configuration::MqttHost, "127.0.0.1"s}, + {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, + {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, + {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, + {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, + }); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 25, false, true, opts); addAdapter(); m_agentTestHelper->getAgent()->start(); } - - void createServer(const ConfigOptions& options) { + + void createServer(const ConfigOptions& options) + { using namespace mtconnect::configuration; ConfigOptions opts(options); MergeOptions(opts, {{ServerIp, "127.0.0.1"s}, - {MqttPort, 0}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); - m_server = std::make_shared(m_agentTestHelper->m_ioContext, opts); + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); + m_server = std::make_shared( + m_agentTestHelper->m_ioContext, opts); } - + template - bool waitFor(const chrono::duration& time, function pred) { + bool waitFor(const chrono::duration& time, function pred) + { boost::asio::steady_timer timer(m_agentTestHelper->m_ioContext); timer.expires_after(time); bool timeout = false; timer.async_wait([&timeout](boost::system::error_code ec) { - if (!ec) timeout = true; + if (!ec) + timeout = true; }); - while (!timeout && !pred()) { + while (!timeout && !pred()) + { m_agentTestHelper->m_ioContext.run_for(100ms); } timer.cancel(); return pred(); } - - void startServer() { - if (m_server) { + + void startServer() + { + if (m_server) + { bool start = m_server->start(); - if (start) { + if (start) + { m_port = m_server->getPort(); m_agentTestHelper->m_ioContext.run_for(500ms); } } } - - void createClient(const ConfigOptions& options, unique_ptr&& handler) { + + void createClient(const ConfigOptions& options, unique_ptr&& handler) + { ConfigOptions opts(options); MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, - {MqttPort, m_port}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); + {MqttPort, m_port}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); m_client = make_shared(m_agentTestHelper->m_ioContext, opts, std::move(handler)); } - - bool startClient() { + + bool startClient() + { bool started = m_client && m_client->start(); - if (started) { + if (started) + { return waitFor(1s, [this]() { return m_client->isConnected(); }); } return started; } - - void addAdapter(ConfigOptions options = ConfigOptions {}) { + + void addAdapter(ConfigOptions options = ConfigOptions {}) + { m_agentTestHelper->addAdapter(options, "localhost", 0, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } + + void stopAgent() + { + const auto agent = m_agentTestHelper->getAgent(); + if (agent) + { + m_agentTestHelper->getAgent()->stop(); + m_agentTestHelper->m_ioContext.run_for(100ms); + } + } + + void stopClient() + { + if (m_client) + { + m_client->stop(); + m_agentTestHelper->m_ioContext.run_for(500ms); + m_client.reset(); + } + } + + void stopServer() + { + if (m_server) + { + m_server->stop(); + m_agentTestHelper->m_ioContext.run_for(500ms); + m_server.reset(); + } + } }; TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) { bool gotMessage = false; std::string receivedTopic; - + createServer({}); startServer(); - + auto client = mqtt::make_async_client(m_agentTestHelper->m_ioContext.get(), "localhost", m_port); - + client->set_client_id("test_client"); client->set_clean_session(true); client->set_keep_alive_sec(30); - + bool subscribed = false; client->set_connack_handler( - [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { - if (connack_return_code == mqtt::connect_return_code::accepted) - { - auto pid = client->acquire_unique_packet_id(); - client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, - [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); - } - return true; - }); - + [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { + if (connack_return_code == mqtt::connect_return_code::accepted) + { + auto pid = client->acquire_unique_packet_id(); + client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, + [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); + } + return true; + }); + client->set_suback_handler( - [&subscribed](std::uint16_t packet_id, std::vector results) { - subscribed = true; - return true; - }); - + [&subscribed](std::uint16_t packet_id, std::vector results) { + subscribed = true; + return true; + }); + client->set_publish_handler([&gotMessage, &receivedTopic](mqtt::optional packet_id, mqtt::publish_options pubopts, mqtt::buffer topic_name, @@ -209,48 +248,49 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) } return true; }); - + client->async_connect([](mqtt::error_code ec) { ASSERT_FALSE(ec) << "Cannot connect"; }); - + // Wait for subscription to complete ASSERT_TRUE(waitFor(10s, [&subscribed]() { return subscribed; })) - << "Subscription never completed"; - + << "Subscription never completed"; + // Create the agent and wait for its MQTT sink to connect. createAgent(); auto sink = m_agentTestHelper->getMqttEntitySink(); ASSERT_TRUE(sink != nullptr); ASSERT_TRUE(waitFor(10s, [&sink]() { return sink->isConnected(); })) - << "MqttEntitySink failed to connect to broker"; - + << "MqttEntitySink failed to connect to broker"; + gotMessage = false; receivedTopic.clear(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })) - << "Timeout waiting for adapter data. Last topic: " << receivedTopic; - + << "Timeout waiting for adapter data. Last topic: " << receivedTopic; + EXPECT_TRUE(receivedTopic.find("MTConnect/Devices/") == 0) - << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; + << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; EXPECT_TRUE(receivedTopic.find("/Observations/") != std::string::npos) - << "Topic doesn't contain /Observations/: " << receivedTopic; + << "Topic doesn't contain /Observations/: " << receivedTopic; EXPECT_EQ("MTConnect/Devices/000/Observations/p3", receivedTopic) - << "Topic does not match expected format: " << receivedTopic; - + << "Topic does not match expected format: " << receivedTopic; + client->async_disconnect(); + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -268,24 +308,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); // Ensure subscription is active before sending data m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Wait a bit to ensure sink and client are ready m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify Entity JSON format EXPECT_TRUE(receivedJson.contains("dataItemId")); EXPECT_TRUE(receivedJson.contains("timestamp")); @@ -293,44 +333,47 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) EXPECT_TRUE(receivedJson.contains("sequence")); EXPECT_TRUE(receivedJson.contains("type")); EXPECT_TRUE(receivedJson.contains("category")); - + EXPECT_EQ("204", receivedJson["result"].get()); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - receivedJson = json::parse(payload); - if (receivedJson.contains("name")) + json js = json::parse(payload); + if (js.contains("name")) { gotMessage = true; } + receivedJson = js; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify optional fields if (receivedJson.contains("name")) { @@ -340,19 +383,21 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { EXPECT_TRUE(receivedJson["subType"].is_string()); } + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotSample = false; json receivedJson; - + handler->m_receive = [&gotSample, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -371,42 +416,45 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); - + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|z2|204"); ASSERT_TRUE(waitFor(10s, [&gotSample]() { return gotSample; })); - + EXPECT_EQ("SAMPLE", receivedJson["category"].get()); EXPECT_EQ("204.000000", receivedJson["result"].get()); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotEvent = false; json receivedJson; - + handler->m_receive = [&gotEvent, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - try { + try + { receivedJson = json::parse(payload); if (receivedJson.contains("category") && receivedJson["category"] == "EVENT" && receivedJson.contains("dataItemId") && receivedJson["dataItemId"] == "p4" && @@ -414,45 +462,50 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) { gotEvent = true; } - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|p4|READY"); ASSERT_TRUE(waitFor(10s, [&gotEvent]() { return gotEvent; })); - + EXPECT_EQ("EVENT", receivedJson["category"].get()); EXPECT_EQ("READY", receivedJson["result"].get()); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotCondition = false; json receivedJson; - + handler->m_receive = [&gotCondition, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - try { + try + { receivedJson = json::parse(payload); if (receivedJson.contains("category") && receivedJson["category"] == "CONDITION" && receivedJson.contains("dataItemId") && receivedJson["dataItemId"] == "zlc" && @@ -460,46 +513,50 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) { gotCondition = true; } - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); + "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); ASSERT_TRUE(waitFor(10s, [&gotCondition]() { return gotCondition; })); - + EXPECT_EQ("CONDITION", receivedJson["category"].get()); EXPECT_EQ("FAULT", receivedJson["level"].get()); if (receivedJson.contains("nativeCode")) { EXPECT_EQ("1234", receivedJson["nativeCode"].get()); } + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotAvailable = false; std::string availabilityValue; - + handler->m_receive = [&gotAvailable, &availabilityValue](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -509,31 +566,33 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) gotAvailable = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&gotAvailable]() { return gotAvailable; })); EXPECT_EQ("AVAILABLE", availabilityValue); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); int messageCount = 0; - + handler->m_receive = [&messageCount](std::shared_ptr client, const std::string& topic, const std::string& payload) { if (topic.find("/Observations/") != std::string::npos) @@ -541,31 +600,33 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) messageCount++; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); ASSERT_TRUE(waitFor(10s, [&messageCount]() { return messageCount > 0; })); EXPECT_GT(messageCount, 0); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotUnavailable = false; json receivedJson; - + handler->m_receive = [&gotUnavailable, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -575,20 +636,22 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) gotUnavailable = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Initial observations should include UNAVAILABLE values ASSERT_TRUE(waitFor(10s, [&gotUnavailable]() { return gotUnavailable; })); EXPECT_EQ("UNAVAILABLE", receivedJson["result"].get()); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_authentication) @@ -597,24 +660,23 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_authentication) options["MqttUserName"] = "mtconnect"; options["MqttPassword"] = "password123"; options["MqttClientId"] = "auth-client"; - + createServer({}); startServer(); - + bool connected = false; auto handler = std::make_unique(); - handler->m_connected = [&connected](std::shared_ptr) { - connected = true; - }; - + handler->m_connected = [&connected](std::shared_ptr) { connected = true; }; + createClient(options, std::move(handler)); startClient(); - + auto start = std::chrono::steady_clock::now(); - while (!connected && std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { + while (!connected && std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) + { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } - + ASSERT_TRUE(connected) << "MQTT client did not connect with authentication"; } @@ -623,25 +685,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_qos_levels) ConfigOptions options; options["MqttQOS"] = "exactly_once"; options["MqttClientId"] = "qos-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool received = false; - handler->m_receive = [&received](std::shared_ptr client, const std::string& topic, const std::string& payload) { - received = true; - }; - + handler->m_receive = [&received](std::shared_ptr client, const std::string& topic, + const std::string& payload) { received = true; }; + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&received]() { return received; })); } @@ -650,29 +711,33 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) ConfigOptions options; options["MqttRetain"] = "true"; options["MqttClientId"] = "retain-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool retainedReceived = false; std::string retainedPayload; - handler->m_receive = [&retainedReceived, &retainedPayload](std::shared_ptr client, const std::string& topic, const std::string& payload) { + handler->m_receive = [&retainedReceived, &retainedPayload](std::shared_ptr client, + const std::string& topic, + const std::string& payload) { retainedReceived = true; retainedPayload = payload; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&retainedReceived]() { return retainedReceived; })); ASSERT_FALSE(retainedPayload.empty()); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) @@ -680,27 +745,31 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) ConfigOptions options; options["MqttLastWillTopic"] = "MTConnect/Probe/J55-411045-cpp/Availability"; options["MqttClientId"] = "lastwill-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool lastWillReceived = false; - handler->m_receive = [&lastWillReceived](std::shared_ptr client, const std::string& topic, const std::string& payload) { - if (topic.find("Availability") != std::string::npos) { + handler->m_receive = [&lastWillReceived](std::shared_ptr client, + const std::string& topic, const std::string& payload) { + if (topic.find("Availability") != std::string::npos) + { lastWillReceived = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + m_client->stop(); ASSERT_TRUE(waitFor(5s, [&lastWillReceived]() { return lastWillReceived; })); + + stopClient(); } diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index 13a047719..262e25081 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -252,8 +252,10 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), boost::asio::detached); + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), + boost::asio::detached); while (!m_done && !m_failed && m_context.run_for(20ms) > 0) ; diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index 07044401f..91b7a0e0f 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -241,8 +241,9 @@ TEST_F(WebsocketsTest, should_make_simple_request) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), - "{\"id\":\"1\",\"request\":\"probe\"}"s, std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"id\":\"1\",\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -270,8 +271,9 @@ TEST_F(WebsocketsTest, should_return_error_when_there_is_no_id) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -362,8 +364,7 @@ TEST_F(WebsocketsTest, should_return_error_when_bad_json_is_sent) start(); startClient(); - asio::spawn(m_context, - std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), + asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -399,7 +400,7 @@ TEST_F(WebsocketsTest, should_return_multiple_errors_when_parameters_are_invalid std::bind(&Client::request, m_client.get(), R"DOC({"id": 3, "request": "sample", "interval": 99999999999,"to": -1 })DOC", std::placeholders::_1), - boost::asio::detached); + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); From b7ab1669b61828fb3d899b7c9d09a7693f4bcc00 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Mon, 3 Nov 2025 13:03:14 +0100 Subject: [PATCH 32/84] Added check to make sure unavailable check excludes conditions for validation --- test_package/mqtt_entity_sink_test.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index bb868908e..46110ce9b 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -277,6 +277,8 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) << "Topic does not match expected format: " << receivedTopic; client->async_disconnect(); + + stopAgent(); stopClient(); } @@ -630,10 +632,15 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) handler->m_receive = [&gotUnavailable, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - receivedJson = json::parse(payload); - if (receivedJson["result"] == "UNAVAILABLE") + + json j = json::parse(payload); + if (j["category"] != "CONDITION") { - gotUnavailable = true; + if (j["result"] == "UNAVAILABLE") + { + gotUnavailable = true; + } + receivedJson = j; } }; @@ -648,6 +655,9 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); // Initial observations should include UNAVAILABLE values + // The issue is the initial values will be received in a random order and the conditions have a + // level instead of a result. If in the interviening time a condition is received, the json may + // not be correct. ASSERT_TRUE(waitFor(10s, [&gotUnavailable]() { return gotUnavailable; })); EXPECT_EQ("UNAVAILABLE", receivedJson["result"].get()); From 08caeea2ac4c2904a5c228ab09f7b8f8b7700808 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Mon, 3 Nov 2025 13:16:07 +0100 Subject: [PATCH 33/84] Exclude the agent device as well --- test_package/mqtt_entity_sink_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index 46110ce9b..3a6a513b2 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -634,7 +634,7 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) const std::string& payload) { json j = json::parse(payload); - if (j["category"] != "CONDITION") + if (j["category"] != "CONDITION" && topic.starts_with("MTConnect/Devices/000/Observations/")) { if (j["result"] == "UNAVAILABLE") { From bb0aa3542596d989d04e4e8893236a882ccd9745 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 5 Nov 2025 13:35:47 +0100 Subject: [PATCH 34/84] Version 2.6.0.5 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c89d74038..eee1e05a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 4) +set(AGENT_VERSION_BUILD 5) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From a0a763bb46807de0d3f4d5412cb8da7da0131272 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 11 Nov 2025 15:14:17 +0100 Subject: [PATCH 35/84] Fixed validation wrt samples --- src/mtconnect/configuration/agent_config.cpp | 2 + src/mtconnect/configuration/agent_config.hpp | 4 + src/mtconnect/device_model/device.cpp | 2 +- src/mtconnect/pipeline/validator.hpp | 98 ++++++++++---------- test_package/observation_validation_test.cpp | 66 ++++++++++++- 5 files changed, 122 insertions(+), 50 deletions(-) diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 1d4c31632..36a182d48 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -940,6 +940,8 @@ namespace mtconnect::configuration { loadAdapters(config, options); + m_afterAgentHooks.exec(*this); + #ifdef WITH_PYTHON configurePython(config, options); #endif diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 593ae6dca..742d1261b 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -93,6 +93,9 @@ namespace mtconnect { /// @brief Get the callback manager after the agent is created /// @return the callback manager auto &afterAgentHooks() { return m_afterAgentHooks; } + /// @brief Get the callback manager after the config has completed + /// @return the callback manager + auto &afterConfigHooks() { return m_afterConfigHooks; } /// @brief Get the callback manager after the agent is started /// @return the callback manager auto &beforeStartHooks() { return m_beforeStartHooks; } @@ -402,6 +405,7 @@ namespace mtconnect { #endif HookManager m_afterAgentHooks; + HookManager m_afterConfigHooks; HookManager m_beforeStartHooks; HookManager m_beforeStopHooks; }; diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index a6e7a9250..b1013588e 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -84,7 +84,7 @@ namespace mtconnect { { if (auto it = m_dataItems.get().find(*name); it != m_dataItems.get().end()) { - LOG(warning) << "Device " << getName() << ": Duplicate source '" << *name + LOG(warning) << "Device " << getName() << ": Duplicate name '" << *name << "' found in data item '" << di->getId() << "'. Previous data item: '" << it->lock()->getId() << '\''; LOG(warning) << " Name '" << *name diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index 21f692414..e6128228e 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -37,11 +37,11 @@ namespace mtconnect::pipeline { public: Validator(const Validator &) = default; Validator(PipelineContextPtr context) - : Transform("Validator"), m_contract(context->m_contract.get()) + : Transform("Validator"), m_contract(context->m_contract.get()) { m_guard = TypeGuard(RUN) || TypeGuard(SKIP); } - + /// @brief validate the Event /// @param entity The Event entity /// @returns modified entity with quality and deprecated properties @@ -50,78 +50,80 @@ namespace mtconnect::pipeline { using namespace observation; using namespace mtconnect::validation::observations; auto obs = std::dynamic_pointer_cast(entity); - + auto &value = obs->getValue(); + + bool valid = true; auto di = obs->getDataItem(); - if (obs->isUnavailable() || di->isDataSet()) - { - obs->setProperty("quality", std::string("VALID")); - } - else if (auto evt = std::dynamic_pointer_cast(obs)) + if (!obs->isUnavailable() && !di->isDataSet()) { - auto &value = evt->getValue(); - - // Optimize - auto vocab = ControlledVocabularies.find(evt->getName()); - if (vocab != ControlledVocabularies.end()) + if (auto evt = std::dynamic_pointer_cast(obs)) { - auto &lits = vocab->second; - if (lits.size() != 0) + auto vocab = ControlledVocabularies.find(evt->getName()); + if (vocab != ControlledVocabularies.end()) { - auto lit = lits.find(value); - if (lit != lits.end()) + auto sv = std::get_if(&value); + auto &lits = vocab->second; + if (lits.size() != 0 && sv != nullptr) { - // Check if it has not been introduced yet - if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first) + auto lit = lits.find(*sv); + if (lit != lits.end()) { - evt->setProperty("quality", std::string("INVALID")); + // Check if it has not been introduced yet + if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first) + valid = false; + + // Check if deprecated + if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second) + { + evt->setProperty("deprecated", true); + } } else { - evt->setProperty("quality", std::string("VALID")); - } - - // Check if deprecated - if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second) - { - evt->setProperty("deprecated", true); + valid = false; } } - else + else if (lits.size() != 0) { - evt->setProperty("quality", std::string("INVALID")); - // Log once - auto &id = di->getId(); - if (m_logOnce.count(id) < 1) - { - LOG(warning) << "DataItem '" << id << "': Invalid value for '" << evt->getName() - << "': '" << evt->getValue() << '\''; - m_logOnce.insert(id); - } - else - { - LOG(trace) << "DataItem '" << id << "': Invalid value for '" << evt->getName() - << "': '" << evt->getValue() << '\''; - } + valid = false; } } else { - evt->setProperty("quality", std::string("VALID")); + evt->setProperty("quality", std::string("UNVERIFIABLE")); } } - else + else if (auto spl = std::dynamic_pointer_cast(obs)) { - evt->setProperty("quality", std::string("UNVERIFIABLE")); + if (!(spl->hasProperty("quality") || std::holds_alternative(value) || + std::holds_alternative(value))) + valid = false; } } - else if (auto spl = std::dynamic_pointer_cast(obs)) + + if (!valid) { + obs->setProperty("quality", std::string("INVALID")); + // Log once + auto &id = di->getId(); + if (m_logOnce.count(id) < 1) + { + Value v = value; + ConvertValueToType(v, ValueType::STRING); + LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() + << "': '" << std::get(value) << '\''; + m_logOnce.insert(id); + } + else + { + LOG(trace) << "DataItem '" << id << "': Invalid value for '" << obs->getName(); + } } - else + else if (!obs->hasProperty("quality")) { obs->setProperty("quality", std::string("VALID")); } - + return next(std::move(obs)); } diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index df0be79f3..8908233cb 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -246,7 +246,7 @@ TEST_F(ObservationValidationTest, should_be_invalid_if_entry_has_not_been_introd ASSERT_FALSE(evt->hasProperty("deprecated")); } -TEST_F(ObservationValidationTest, should_validate_data_item_types) +TEST_F(ObservationValidationTest, should_validate_invalid_sample_value) { auto contract = static_cast(m_context->m_contract.get()); contract->m_schemaVersion = SCHEMA_VERSION(2, 5); @@ -284,6 +284,59 @@ TEST_F(ObservationValidationTest, should_validate_data_item_types) } } +TEST_F(ObservationValidationTest, should_validate_sample) +{ + auto contract = static_cast(m_context->m_contract.get()); + contract->m_schemaVersion = SCHEMA_VERSION(2, 5); + + shared_ptr mapper; + mapper = make_shared(m_context, "", 2); + mapper->bind(m_validator); + + ErrorList errors; + m_dataItem = DataItem::make( + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + + auto ts = make_shared(); + ts->m_tokens = {{"pos"s, "1.234"s}}; + ts->m_timestamp = chrono::system_clock::now(); + ts->setProperty("timestamp", ts->m_timestamp); + + auto observations = (*mapper)(ts); + auto &r = *observations; + ASSERT_EQ(typeid(Observations), typeid(r)); + + auto oblist = observations->getValue(); + ASSERT_EQ(1, oblist.size()); + + auto oi = oblist.begin(); + + { + auto sample = dynamic_pointer_cast(*oi++); + ASSERT_TRUE(sample); + ASSERT_EQ(m_dataItem, sample->getDataItem()); + ASSERT_FALSE(sample->isUnavailable()); + + ASSERT_EQ("VALID", sample->get("quality")); + } +} + +TEST_F(ObservationValidationTest, should_validate_sample_with_int64_value) +{ + ErrorList errors; + m_dataItem = DataItem::make( + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + + auto obs = Observation::make(m_dataItem, {{"VALUE", int64_t(100)}}, m_time, errors); + + auto evt = (*m_validator)(std::move(obs)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); +} + + TEST_F(ObservationValidationTest, should_not_validate_if_validation_is_off) { auto contract = static_cast(m_context->m_contract.get()); @@ -369,3 +422,14 @@ TEST_F(ObservationValidationTest, should_validate_json_data_item_types) ASSERT_EQ("INVALID", sample->get("quality")); } } + +/// @test Validate a a sample +TEST_F(ObservationValidationTest, should_validate_sample_double_value) +{ + ErrorList errors; + auto event = Observation::make(m_dataItem, {{"VALUE", "READY"s}}, m_time, errors); + + auto evt = (*m_validator)(std::move(event)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); +} From 97121091bc7080bc48855ae4467fd25015c25d1e Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 11 Nov 2025 16:55:51 +0100 Subject: [PATCH 36/84] Added better logging and stream operator for a Entity Value --- src/mtconnect/entity/entity.hpp | 72 ++++++++++++++++++++++++++++ src/mtconnect/pipeline/validator.hpp | 4 +- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index c70872596..fa34a8b0f 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -716,5 +716,77 @@ namespace mtconnect { return changed; } + + /// @brief variant visitor to output Value to stream + struct StreamOutputVisitor + { + /// @brief constructor + /// @param os the output stream + StreamOutputVisitor(std::ostream& os) : m_os(os) {} + + void operator()(const std::monostate&) { + m_os << "null"; + } + + void operator()(const EntityPtr& entity) { + const auto &id = entity->getIdentity(); + m_os << "Entity(" << entity->getName() << ":"; + StreamOutputVisitor visitor(m_os); + std::visit(visitor, id); + m_os << ")"; + } + + void operator()(const EntityList& list) { + m_os << "EntityList["; + for (auto e : list) { + StreamOutputVisitor visitor(m_os); + visitor(e); + m_os << " "; + } + m_os << "]"; + } + + void operator()(const DataSet& dataSet) { + m_os << "DataSet(" << dataSet.size() << " items)"; + } + + void operator()(const QName& qname) { + m_os << qname.str(); + } + + void operator()(const Vector& vec) { + m_os << "Vector["; + for (const auto &v : vec) { + m_os << v << " "; + } + m_os << "]"; + } + + template + void operator()(const T& value) { + m_os << value; + } + + std::ostream& m_os; + }; + + /// @brief output operator for Value + /// @param os the output stream + /// @param v the Value to output + inline std::ostream& operator<<(std::ostream& os, const Value &v) { + StreamOutputVisitor visitor(os); + std::visit(visitor, v); + return os; + } + + /// @brief output operator for Value + /// @param os the output stream + /// @param v the Value to output + inline std::ostream& operator<<(std::ostream& os, const EntityPtr &v) { + StreamOutputVisitor visitor(os); + visitor(v); + return os; + } + } // namespace entity } // namespace mtconnect diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index e6128228e..d1f624f49 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -108,10 +108,8 @@ namespace mtconnect::pipeline { auto &id = di->getId(); if (m_logOnce.count(id) < 1) { - Value v = value; - ConvertValueToType(v, ValueType::STRING); LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() - << "': '" << std::get(value) << '\''; + << "': '" << value << '\''; m_logOnce.insert(id); } else From 44f8dcbc5ace9e5a9cf65764f6d077f8c858ff4f Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 11 Nov 2025 18:22:39 +0100 Subject: [PATCH 37/84] Fixed typo an changed version to 2.6.0.6 --- CMakeLists.txt | 2 +- test_package/observation_validation_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eee1e05a4..30f049785 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 5) +set(AGENT_VERSION_BUILD 6) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index 8908233cb..9799e9cf9 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -423,7 +423,7 @@ TEST_F(ObservationValidationTest, should_validate_json_data_item_types) } } -/// @test Validate a a sample +/// @test Validate a sample TEST_F(ObservationValidationTest, should_validate_sample_double_value) { ErrorList errors; From 19c658ab83b72e19aaf3356c9dd6fb7299164fc9 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 12 Nov 2025 13:24:24 +0100 Subject: [PATCH 38/84] Conan build on alpine for m4 now requires bash. apk added bash --- docker/alpine/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index ec7599cf2..bffa8a23b 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -29,7 +29,7 @@ # --------------------------------------------------------------------- # base image - alpine 3.18 -FROM alpine:3.19 AS os +FROM alpine:3.22 AS os # --------------------------------------------------------------------- # build @@ -53,6 +53,7 @@ ENV CONAN_PROFILE="$HOME/agent/cppagent/conan/profiles/docker" RUN apk --update add \ autoconf \ automake \ + bash \ cmake \ g++ \ gcompat \ From f51da8f6d3eab9ef47a147b414339e22efef95ad Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 14 Nov 2025 14:41:32 +0100 Subject: [PATCH 39/84] Fixed alpine build issues --- agent_lib/CMakeLists.txt | 2 +- cmake/osx_no_app_or_frameworks.cmake | 2 +- conanfile.py | 6 +++--- src/mtconnect/entity/requirement.cpp | 5 +++-- src/mtconnect/pipeline/response_document.cpp | 15 +-------------- src/mtconnect/pipeline/timestamp_extractor.hpp | 4 ++-- src/mtconnect/printer/xml_printer.cpp | 2 +- src/mtconnect/printer/xml_printer_helper.hpp | 2 +- src/mtconnect/sink/rest_sink/error.hpp | 2 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 7 +------ .../source/adapter/shdr/shdr_adapter.hpp | 1 - src/mtconnect/utilities.cpp | 1 - src/mtconnect/utilities.hpp | 18 ++++++++++-------- 13 files changed, 25 insertions(+), 42 deletions(-) diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 4e979e0c1..6c22ca22a 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -393,7 +393,7 @@ target_include_directories( target_link_libraries( agent_lib PUBLIC - boost::boost LibXml2::LibXml2 date::date-tz openssl::openssl + boost::boost LibXml2::LibXml2 date::date openssl::openssl nlohmann_json::nlohmann_json mqtt_cpp::mqtt_cpp rapidjson BZip2::BZip2 diff --git a/cmake/osx_no_app_or_frameworks.cmake b/cmake/osx_no_app_or_frameworks.cmake index 4b39c4054..cc7be2e47 100644 --- a/cmake/osx_no_app_or_frameworks.cmake +++ b/cmake/osx_no_app_or_frameworks.cmake @@ -8,6 +8,6 @@ set(CMAKE_FIND_FRAMEWORK NEVER FORCE) # ms suffix which was added post 11. if (APPLE) # set(COVERAGE_FLAGS "-fcoverage-mapping -fprofile-instr-generate") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++17") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++20") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-local-typedef -Wno-deprecated-declarations -Wall") endif() diff --git a/conanfile.py b/conanfile.py index 94213e50e..e5662f96f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -68,7 +68,7 @@ class MTConnectAgentConan(ConanFile): "openssl*:shared": False, - "date*:use_system_tz_db": True + "date*:header_only": True } exports_sources = "*", "!build", "!test_package/build", "!*~" @@ -112,14 +112,14 @@ def tool_requires_version(self, package, version): def build_requirements(self): self.tool_requires_version("cmake", [3, 26, 4]) if self.options.with_docs: - self.tool_requires_version("doxygen", [1, 14, 0]) + self.tool_requires_version("doxygen", [1, 15, 0]) def requirements(self): self.requires("boost/1.88.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("libxml2/2.14.5", headers=True, libs=True, visible=True, transitive_headers=True, transitive_libs=True) self.requires("date/3.0.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("nlohmann_json/3.12.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) - self.requires("openssl/3.5.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) + self.requires("openssl/3.6.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("rapidjson/cci.20230929", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("mqtt_cpp/13.2.2", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("bzip2/1.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) diff --git a/src/mtconnect/entity/requirement.cpp b/src/mtconnect/entity/requirement.cpp index 51c2848d5..1422144bd 100644 --- a/src/mtconnect/entity/requirement.cpp +++ b/src/mtconnect/entity/requirement.cpp @@ -186,12 +186,13 @@ namespace mtconnect { // If there isa a time portion in the string, parse the time if (arg.find('T') != string::npos) { - in >> std::setw(6) >> date::parse("%FT%T", ts); + in >> std::setw(6); + date::from_stream(in, "%FT%T", ts); } else { // Just parse the date - in >> date::parse("%F", ts); + date::from_stream(in, "%F", ts); } } void operator()(const string &arg, Vector &r) diff --git a/src/mtconnect/pipeline/response_document.cpp b/src/mtconnect/pipeline/response_document.cpp index d538488c6..5f376f6bb 100644 --- a/src/mtconnect/pipeline/response_document.cpp +++ b/src/mtconnect/pipeline/response_document.cpp @@ -23,6 +23,7 @@ #include #include +#include "mtconnect/utilities.hpp" #include "mtconnect/asset/asset.hpp" #include "mtconnect/device_model/device.hpp" #include "mtconnect/entity/data_set.hpp" @@ -280,20 +281,6 @@ namespace mtconnect::pipeline { }); } - inline static Timestamp parseTimestamp(const std::string value) - { - Timestamp ts; - istringstream in(value); - in >> std::setw(6) >> date::parse("%FT%T", ts); - if (!in.good()) - { - LOG(error) << "Cound not parse XML timestamp: " << value; - ts = std::chrono::system_clock::now(); - } - - return ts; - } - inline static DataItemPtr findDataItem(const std::string &name, DevicePtr device, const entity::Properties &properties) { diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 651c77350..4208e057e 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -84,7 +84,6 @@ namespace mtconnect::pipeline { Now now = DefaultNow) { using namespace std; - using namespace date; using namespace chrono; using namespace chrono_literals; using namespace date; @@ -103,7 +102,8 @@ namespace mtconnect::pipeline { if (has_t) { istringstream in(timestamp.data()); - in >> std::setw(6) >> date::parse("%FT%T", result); + in >> std::setw(6); + date::from_stream(in, "%FT%T", result); if (!in.good()) { result = now(); diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 06c0da048..f4b5bafa2 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -94,7 +94,7 @@ namespace mtconnect::printer { xmlFreeTextWriter(m_writer); m_writer = nullptr; } - return string((char *)m_buf->content, m_buf->use); + return std::string((char *)xmlBufferContent(m_buf), xmlBufferLength(m_buf)); } protected: diff --git a/src/mtconnect/printer/xml_printer_helper.hpp b/src/mtconnect/printer/xml_printer_helper.hpp index fb5d4b223..74a45cf24 100644 --- a/src/mtconnect/printer/xml_printer_helper.hpp +++ b/src/mtconnect/printer/xml_printer_helper.hpp @@ -68,7 +68,7 @@ namespace mtconnect::printer { xmlFreeTextWriter(m_writer); m_writer = nullptr; } - return std::string((char *)m_buf->content, m_buf->use); + return std::string((char *)xmlBufferContent(m_buf), xmlBufferLength(m_buf)); } protected: diff --git a/src/mtconnect/sink/rest_sink/error.hpp b/src/mtconnect/sink/rest_sink/error.hpp index 72592b88e..5f5bc44aa 100644 --- a/src/mtconnect/sink/rest_sink/error.hpp +++ b/src/mtconnect/sink/rest_sink/error.hpp @@ -19,7 +19,7 @@ #include -#include +#include #include "mtconnect/entity/entity.hpp" #include "mtconnect/entity/factory.hpp" diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 4bdfbaad2..1f01a8445 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1411,12 +1411,7 @@ namespace mtconnect { Timestamp ts; if (time) { - istringstream in(*time); - in >> std::setw(6) >> date::parse("%FT%T", ts); - if (!in.good()) - { - ts = chrono::system_clock::now(); - } + ts = parseTimestamp(*time); } else { diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp index 15c25eba3..5b84fd249 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp @@ -18,7 +18,6 @@ #pragma once #include -#include #include #include #include diff --git a/src/mtconnect/utilities.cpp b/src/mtconnect/utilities.cpp index d6fb420c8..bf33c4a5f 100644 --- a/src/mtconnect/utilities.cpp +++ b/src/mtconnect/utilities.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 8ccbeb09e..3e4f65785 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -31,7 +31,6 @@ #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include #include #include +#include #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" @@ -254,9 +254,7 @@ namespace mtconnect { using namespace std::chrono; constexpr char ISO_8601_FMT[] = "%Y-%m-%dT%H:%M:%SZ"; #ifdef _WINDOWS - namespace tzchrono = std::chrono; -#else - namespace tzchrono = date; + #endif switch (format) @@ -269,9 +267,12 @@ namespace mtconnect { return date::format(ISO_8601_FMT, date::floor(timePoint)); case LOCAL: { - auto zone = tzchrono::current_zone(); - auto zt = date::zoned_time(zone, timePoint); - return date::format("%Y-%m-%dT%H:%M:%S%z", zt); + time_t t = time(nullptr); + struct tm local; + localtime_r(&t, &local); + char buf[64]; + strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &local); + return string(buf); } } @@ -776,7 +777,8 @@ namespace mtconnect { { Timestamp ts; std::istringstream in(timestamp); - in >> std::setw(6) >> date::parse("%FT%T", ts); + in >> std::setw(6); + date::from_stream(in, "%FT%T", ts); if (!in.good()) { ts = std::chrono::system_clock::now(); From 4a2f1624656d68cf6c712791535bc714f913ae41 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 14 Nov 2025 14:59:01 +0100 Subject: [PATCH 40/84] Version 2.6.0.7 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30f049785..8aca0d680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 6) +set(AGENT_VERSION_BUILD 7) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From d7a983ee2c51628151b811c2320c11930fc6080b Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 14 Nov 2025 15:46:45 +0100 Subject: [PATCH 41/84] Added define for Windows to map from localtime_r to _s --- src/mtconnect/utilities.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 3e4f65785..d8f12a5ed 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -254,7 +254,7 @@ namespace mtconnect { using namespace std::chrono; constexpr char ISO_8601_FMT[] = "%Y-%m-%dT%H:%M:%SZ"; #ifdef _WINDOWS - +#define localtime_r(t, tm) localtime_s(tm, t) #endif switch (format) From 22bacc6ce9e8aaf330cf39129bd8edcc62859e21 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 15 Nov 2025 13:47:56 +0100 Subject: [PATCH 42/84] Fixed issue in windows with inderterminism of mqtt entity test for topic structure --- src/mtconnect/agent.cpp | 2 +- src/mtconnect/configuration/agent_config.cpp | 2 +- src/mtconnect/entity/entity.hpp | 62 +- src/mtconnect/pipeline/response_document.cpp | 2 +- src/mtconnect/pipeline/validator.hpp | 18 +- src/mtconnect/utilities.hpp | 2 +- test_package/agent_test.cpp | 856 +++++++++---------- test_package/mqtt_entity_sink_test.cpp | 359 ++++---- test_package/observation_validation_test.cpp | 31 +- 9 files changed, 673 insertions(+), 661 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 52544662e..7f27ad008 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -1019,7 +1019,7 @@ namespace mtconnect { stringstream msg; msg << "Device " << *device->getUuid() << " already exists. " << " Update not supported yet"; - throw msg; + throw msg.str(); } else { diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 36a182d48..ab63fa019 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -941,7 +941,7 @@ namespace mtconnect::configuration { loadAdapters(config, options); m_afterAgentHooks.exec(*this); - + #ifdef WITH_PYTHON configurePython(config, options); #endif diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index fa34a8b0f..e04bf24b0 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -716,73 +716,75 @@ namespace mtconnect { return changed; } - + /// @brief variant visitor to output Value to stream struct StreamOutputVisitor { /// @brief constructor /// @param os the output stream - StreamOutputVisitor(std::ostream& os) : m_os(os) {} - - void operator()(const std::monostate&) { - m_os << "null"; - } - - void operator()(const EntityPtr& entity) { + StreamOutputVisitor(std::ostream &os) : m_os(os) {} + + void operator()(const std::monostate &) { m_os << "null"; } + + void operator()(const EntityPtr &entity) + { const auto &id = entity->getIdentity(); m_os << "Entity(" << entity->getName() << ":"; StreamOutputVisitor visitor(m_os); std::visit(visitor, id); m_os << ")"; } - - void operator()(const EntityList& list) { + + void operator()(const EntityList &list) + { m_os << "EntityList["; - for (auto e : list) { + for (auto e : list) + { StreamOutputVisitor visitor(m_os); visitor(e); m_os << " "; } m_os << "]"; } - - void operator()(const DataSet& dataSet) { - m_os << "DataSet(" << dataSet.size() << " items)"; - } - - void operator()(const QName& qname) { - m_os << qname.str(); - } - - void operator()(const Vector& vec) { + + void operator()(const DataSet &dataSet) { m_os << "DataSet(" << dataSet.size() << " items)"; } + + void operator()(const QName &qname) { m_os << qname.str(); } + + void operator()(const Vector &vec) + { m_os << "Vector["; - for (const auto &v : vec) { + for (const auto &v : vec) + { m_os << v << " "; } m_os << "]"; } - + template - void operator()(const T& value) { + void operator()(const T &value) + { m_os << value; } - - std::ostream& m_os; + + std::ostream &m_os; }; - + /// @brief output operator for Value /// @param os the output stream /// @param v the Value to output - inline std::ostream& operator<<(std::ostream& os, const Value &v) { + inline std::ostream &operator<<(std::ostream &os, const Value &v) + { StreamOutputVisitor visitor(os); std::visit(visitor, v); return os; } - + /// @brief output operator for Value /// @param os the output stream /// @param v the Value to output - inline std::ostream& operator<<(std::ostream& os, const EntityPtr &v) { + inline std::ostream &operator<<(std::ostream &os, const EntityPtr &v) + { StreamOutputVisitor visitor(os); visitor(v); return os; diff --git a/src/mtconnect/pipeline/response_document.cpp b/src/mtconnect/pipeline/response_document.cpp index 5f376f6bb..d49ee36d8 100644 --- a/src/mtconnect/pipeline/response_document.cpp +++ b/src/mtconnect/pipeline/response_document.cpp @@ -23,13 +23,13 @@ #include #include -#include "mtconnect/utilities.hpp" #include "mtconnect/asset/asset.hpp" #include "mtconnect/device_model/device.hpp" #include "mtconnect/entity/data_set.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/observation/observation.hpp" #include "mtconnect/pipeline/timestamp_extractor.hpp" +#include "mtconnect/utilities.hpp" using namespace std; diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index d1f624f49..29e92af9e 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -37,11 +37,11 @@ namespace mtconnect::pipeline { public: Validator(const Validator &) = default; Validator(PipelineContextPtr context) - : Transform("Validator"), m_contract(context->m_contract.get()) + : Transform("Validator"), m_contract(context->m_contract.get()) { m_guard = TypeGuard(RUN) || TypeGuard(SKIP); } - + /// @brief validate the Event /// @param entity The Event entity /// @returns modified entity with quality and deprecated properties @@ -51,7 +51,7 @@ namespace mtconnect::pipeline { using namespace mtconnect::validation::observations; auto obs = std::dynamic_pointer_cast(entity); auto &value = obs->getValue(); - + bool valid = true; auto di = obs->getDataItem(); if (!obs->isUnavailable() && !di->isDataSet()) @@ -71,7 +71,7 @@ namespace mtconnect::pipeline { // Check if it has not been introduced yet if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first) valid = false; - + // Check if deprecated if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second) { @@ -96,11 +96,11 @@ namespace mtconnect::pipeline { else if (auto spl = std::dynamic_pointer_cast(obs)) { if (!(spl->hasProperty("quality") || std::holds_alternative(value) || - std::holds_alternative(value))) + std::holds_alternative(value))) valid = false; } } - + if (!valid) { obs->setProperty("quality", std::string("INVALID")); @@ -108,8 +108,8 @@ namespace mtconnect::pipeline { auto &id = di->getId(); if (m_logOnce.count(id) < 1) { - LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() - << "': '" << value << '\''; + LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() << "': '" + << value << '\''; m_logOnce.insert(id); } else @@ -121,7 +121,7 @@ namespace mtconnect::pipeline { { obs->setProperty("quality", std::string("VALID")); } - + return next(std::move(obs)); } diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index d8f12a5ed..db07add7a 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,6 @@ #include #include #include -#include #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 41a60bf69..5039741e1 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -63,7 +63,7 @@ class AgentTest : public testing::Test public: typedef std::map map_type; using queue_type = list; - + protected: void SetUp() override { @@ -71,19 +71,19 @@ class AgentTest : public testing::Test m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 25, true); m_agentId = to_string(getCurrentTimeInSec()); } - + void TearDown() override { m_agentTestHelper.reset(); } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 7878, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + public: std::string m_agentId; std::unique_ptr m_agentTestHelper; - + std::chrono::milliseconds m_delay {}; }; @@ -91,18 +91,18 @@ TEST_F(AgentTest, Constructor) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.7"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/badPath.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - + ASSERT_THROW(agent->initialize(context), FatalException); agent.reset(); - + agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/test_config.xml", options); - + context = std::make_shared(); context->m_contract = agent->makePipelineContract(); ASSERT_NO_THROW(agent->initialize(context)); @@ -114,17 +114,17 @@ TEST_F(AgentTest, Probe) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); @@ -135,12 +135,12 @@ TEST_F(AgentTest, FailWithDuplicateDeviceUUID) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.5"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/dup_uuid.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - + ASSERT_THROW(agent->initialize(context), FatalException); } @@ -159,7 +159,7 @@ TEST_F(AgentTest, should_return_2_6_error_for_unknown_device) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/LinuxCN/probe"); string message = (string) "Could not find the device 'LinuxCN'"; @@ -180,7 +180,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -188,7 +188,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -203,7 +203,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { QueryMap query {{"path", "//////Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -212,7 +212,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//////Linear"); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -221,7 +221,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//Axes?//Linear"); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -243,12 +243,12 @@ TEST_F(AgentTest, should_handle_a_correct_path) "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:ComponentStream", 1); } - + { QueryMap query { - {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; + {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:SpindleSpeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:Load", "UNAVAILABLE"); @@ -266,7 +266,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -274,7 +274,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -288,10 +288,10 @@ TEST_F(AgentTest, should_report_an_invalid_uri) TEST_F(AgentTest, should_report_a_2_6_invalid_uri) { using namespace rest_sink; - + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/bad_path"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -301,25 +301,25 @@ TEST_F(AgentTest, should_report_a_2_6_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/bad/path/"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/LinuxCNC/current/blah"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } @@ -329,21 +329,21 @@ TEST_F(AgentTest, should_handle_current_at) { QueryMap query; PARSE_XML_RESPONSE_QUERY("/current", query); - + addAdapter(); - + // Get the current position auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); char line[80] = {0}; - + // Add many events for (int i = 1; i <= 100; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 0; i < 100; i++) { @@ -354,7 +354,7 @@ TEST_F(AgentTest, should_handle_current_at) // m_agentTestHelper->printsession(); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test buffer wrapping // Add a large many events for (int i = 101; i <= 301; i++) @@ -362,7 +362,7 @@ TEST_F(AgentTest, should_handle_current_at) sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 100; i < 301; i++) { @@ -371,7 +371,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Check the first couple of items in the list for (int j = 0; j < 10; j++) { @@ -381,7 +381,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test out of range... { auto i = circ.getSequence() - circ.getBufferSize() - seq - 1; @@ -397,25 +397,25 @@ TEST_F(AgentTest, should_handle_current_at) TEST_F(AgentTest, should_handle_64_bit_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Initialize the sliding buffer at a very large number. auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + uint64_t start = (((uint64_t)1) << 48) + 1317; circ.setSequence(start); - + // Add many events for (int i = 1; i <= 500; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (uint64_t i = start + 300; i < start + 500; i++) { @@ -429,22 +429,22 @@ TEST_F(AgentTest, should_handle_64_bit_current_at) TEST_F(AgentTest, should_report_out_of_range_for_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + { query["at"] = to_string(seq); sprintf(line, "'at' must be less than %d", int32_t(seq)); @@ -452,9 +452,9 @@ TEST_F(AgentTest, should_report_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", line); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than %d", int32_t(seq)); @@ -468,21 +468,21 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); auto max = seq - 1; @@ -500,9 +500,9 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than 0"); @@ -523,15 +523,15 @@ TEST_F(AgentTest, AddAdapter) { addAdapter(); } TEST_F(AgentTest, should_download_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->setMaxCachedFileSize(100 * 1024); rest->getFileCache()->registerFile( - uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); - + uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); + // Reqyest the file... PARSE_TEXT_RESPONSE(uri.c_str()); ASSERT_FALSE(m_agentTestHelper->session()->m_body.empty()); @@ -542,13 +542,13 @@ TEST_F(AgentTest, should_download_file) TEST_F(AgentTest, should_report_not_found_when_cannot_find_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "INVALID_URI"); @@ -561,15 +561,15 @@ TEST_F(AgentTest, should_report_2_6_not_found_when_cannot_find_file) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -583,20 +583,20 @@ TEST_F(AgentTest, should_include_composition_ids_in_observations) { auto agent = m_agentTestHelper->m_agent.get(); addAdapter(); - + DataItemPtr motor = agent->getDataItemForDevice("LinuxCNC", "zt1"); ASSERT_TRUE(motor); - + DataItemPtr amp = agent->getDataItemForDevice("LinuxCNC", "zt2"); ASSERT_TRUE(amp); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|zt1|100|zt2|200"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']", "200"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']@compositionId", "zmotor"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']@compositionId", @@ -616,7 +616,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) "query parameter 'count': cannot convert " "string 'NON_INTEGER' to integer"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -625,14 +625,14 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(-size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'count' must not be zero(0)"); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -641,7 +641,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -650,7 +650,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -665,7 +665,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); int size = circ.getBufferSize() + 1; { @@ -681,7 +681,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -698,7 +698,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -712,13 +712,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=500"); @@ -729,13 +729,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=9999999"); @@ -746,13 +746,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be greater than "); value += to_string(-size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=-9999999"); @@ -768,24 +768,24 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) TEST_F(AgentTest, should_process_addapter_data) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Alarm[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); - + "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -799,17 +799,17 @@ TEST_F(AgentTest, should_get_samples_using_next_sequence) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 300; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); { @@ -825,27 +825,27 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_count) addAdapter(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + { query["path"] = "//DataItem[@name='Xact']"; query["from"] = to_string(seq); query["count"] = "10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 20).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -859,29 +859,29 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_negative_count) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "-10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -895,30 +895,30 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines auto start = seq - 20; for (int j = 0; j < 10; j++) @@ -927,18 +927,18 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); query["from"] = to_string(seq - 10); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 5); - + // Make sure we got 10 lines auto start = seq - 10; for (int j = 0; j < 5; j++) @@ -947,7 +947,7 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + // TODO: Test negative conditions // count < 0 // to > nextSequence @@ -963,11 +963,11 @@ TEST_F(AgentTest, should_give_empty_stream_with_no_new_samples) ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable", nullptr); ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", - nullptr); + doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", + nullptr); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:RotaryMode", "SPINDLE"); } - + { auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); QueryMap query {{"from", to_string(circ.getSequence())}}; @@ -980,31 +980,31 @@ TEST_F(AgentTest, should_not_leak_observations_when_added_to_buffer) { auto agent = m_agentTestHelper->m_agent.get(); QueryMap query; - + string device("LinuxCNC"), key("badKey"), value("ON"); SequenceNumber_t seqNum {0}; auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto event1 = circ.getFromBuffer(seqNum); ASSERT_FALSE(event1); - + { query["from"] = to_string(circ.getSequence()); PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); } - + key = "power"; - + auto di2 = agent->getDataItemForDevice(device, key); seqNum = m_agentTestHelper->addToBuffer(di2, {{"VALUE", value}}, chrono::system_clock::now()); auto event2 = circ.getFromBuffer(seqNum); ASSERT_EQ(3, event2.use_count()); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState", "ON"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState[1]", "UNAVAILABLE"); @@ -1017,53 +1017,53 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) #ifndef WIN32 QueryMap query; addAdapter(); - + // Set the sequence number near MAX_UINT32 auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); circ.setSequence(0xFFFFFFA0); SequenceNumber_t seq = circ.getSequence(); ASSERT_EQ((int64_t)0xFFFFFFA0, seq); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + i).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); } - + { query["from"] = to_string(seq); query["count"] = "128"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); - + for (int j = 0; j <= i; j++) { sprintf(line, "//m:DeviceStream//m:Line[%d]@sequence", j + 1); ASSERT_XML_PATH_EQUAL(doc, line, to_string(seq + j).c_str()); } } - + for (int j = 0; j <= i; j++) { query["from"] = to_string(seq + j); query["count"] = "1"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + j).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + j + 1).c_str()); } } - + ASSERT_EQ(uint64_t(0xFFFFFFA0) + 128ul, circ.getSequence()); #endif } @@ -1071,22 +1071,22 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) TEST_F(AgentTest, should_not_allow_duplicates_values) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1098,20 +1098,20 @@ TEST_F(AgentTest, should_not_allow_duplicates_values) TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) { addAdapter({{configuration::FilterDuplicates, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); } - + m_agentTestHelper->m_adapter->disconnected(); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1119,11 +1119,11 @@ TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[4]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->connected(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1143,31 +1143,31 @@ TEST_F(AgentTest, should_handle_auto_available_if_adapter_option_is_set) auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); } - + agent->disconnected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[3]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); @@ -1183,66 +1183,66 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) auto agent = m_agentTestHelper->m_agent.get(); auto adapter = m_agentTestHelper->m_adapter; auto id = adapter->getIdentity(); - + auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); } - + agent->connected(id, devices, false); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->connected(id, devices, false); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 3); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 5); } } @@ -1250,18 +1250,18 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]@timestamp", "2021-02-01T12:00:00Z"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::IgnoreTimestamps, true}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1273,7 +1273,7 @@ TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) TEST_F(AgentTest, InitialTimeSeriesValues) { addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", @@ -1285,41 +1285,41 @@ TEST_F(AgentTest, should_support_dynamic_calibration_data) { addAdapter({{configuration::ConversionRequired, true}}); auto agent = m_agentTestHelper->getAgent(); - + // Add a 10.111000 seconds m_agentTestHelper->m_adapter->protocolCommand( - "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); + "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); auto di = agent->getDataItemForDevice("LinuxCNC", "Yact"); ASSERT_TRUE(di); - + // TODO: Fix conversions auto &conv1 = di->getConverter(); ASSERT_TRUE(conv1); ASSERT_EQ(0.01, conv1->factor()); ASSERT_EQ(200.0, conv1->offset()); - + di = agent->getDataItemForDevice("LinuxCNC", "Zact"); ASSERT_TRUE(di); - + auto &conv2 = di->getConverter(); ASSERT_TRUE(conv2); ASSERT_EQ(0.02, conv2->factor()); ASSERT_EQ(300.0, conv2->offset()); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|Yact|200|Zact|600"); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " - "5119 5119 5118 " - "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); - + "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " + "5119 5119 5118 " + "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='y1']", "4"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='z1']", "18"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", - "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " - "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); + doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", + "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " + "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); } } @@ -1327,32 +1327,32 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_1) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|100"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|103"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[3]", "106"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106|load|108|load|112"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1366,15 +1366,15 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1382,10 +1382,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:32.000666|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:36.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1395,10 +1395,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData( - "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); - + "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1409,9 +1409,9 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:47.50|pos|45|pos|50"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1431,24 +1431,24 @@ TEST_F(AgentTest, period_filter_should_work_with_ignore_timestamps) // Test period filter with ignore timestamps m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::IgnoreTimestamps, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:32.000666|load|103|pos|25"); this_thread::sleep_for(11s); m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:40.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1462,23 +1462,23 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) // Test period filter with relative time m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::RelativeTime, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("0|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("5000|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("11000|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1490,13 +1490,13 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) TEST_F(AgentTest, reset_triggered_should_work) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("TIME1|pcount|0"); m_agentTestHelper->m_adapter->processData("TIME2|pcount|1"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|2"); m_agentTestHelper->m_adapter->processData("TIME4|pcount|0:DAY"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|5"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount[1]", "UNAVAILABLE"); @@ -1513,58 +1513,58 @@ TEST_F(AgentTest, reset_triggered_should_work) TEST_F(AgentTest, should_honor_references_when_getting_current_or_sample) { using namespace device_model; - + m_agentTestHelper->createAgent("/samples/reference_example.xml"); addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + string id = "mf"; auto item = agent->getDataItemForDevice((string) "LinuxCNC", id); auto comp = item->getComponent(); - + auto references = comp->getList("References"); ASSERT_TRUE(references); ASSERT_EQ(3, references->size()); auto reference = references->begin(); - + EXPECT_EQ("DataItemRef", (*reference)->getName()); - + EXPECT_EQ("chuck", (*reference)->get("name")); EXPECT_EQ("c4", (*reference)->get("idRef")); - + auto ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; reference++; - + EXPECT_EQ("door", (*reference)->get("name")); EXPECT_EQ("d2", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; - + reference++; EXPECT_EQ("electric", (*reference)->get("name")); EXPECT_EQ("ele", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::COMPONENT, ref->getReferenceType()); ASSERT_TRUE(ref->getComponent().lock()) << "DataItem was not resolved"; - + // Additional data items should be included { QueryMap query {{"path", "//BarFeederInterface"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); + doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Door']//m:DoorState", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:ChuckState", @@ -1577,34 +1577,34 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) m_agentTestHelper->createAgent("/samples/discrete_example.xml"); addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto msg = agent->getDataItemForDevice("LinuxCNC", "message"); ASSERT_TRUE(msg); ASSERT_EQ(true, msg->isDiscreteRep()); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); @@ -1619,17 +1619,17 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) TEST_F(AgentTest, should_honor_upcase_values) { addAdapter({{configuration::FilterDuplicates, true}, {configuration::UpcaseDataItemValue, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "HELLO"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::UpcaseDataItemValue, false}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "Hello"); @@ -1642,7 +1642,7 @@ TEST_F(AgentTest, should_handle_condition_activation) auto agent = m_agentTestHelper->getAgent(); auto logic = agent->getDataItemForDevice("LinuxCNC", "lp"); ASSERT_TRUE(logic); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/current"); @@ -1652,29 +1652,29 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Unavailable[@dataItemId='lp']", 1); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||XXX"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - "XXX"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + "XXX"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1691,28 +1691,28 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1724,21 +1724,21 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@nativeSeverity", "ALARM_D"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1755,18 +1755,18 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[2]@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " - "set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " + "set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1783,35 +1783,35 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[1]@qualifier", "HIGH"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL|2218|||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" "m:Fault[1]@nativeCode", "4200"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } } @@ -1819,55 +1819,55 @@ TEST_F(AgentTest, should_handle_empty_entry_as_last_pair_from_adapter) { addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto program = agent->getDataItemForDevice("LinuxCNC", "program"); ASSERT_TRUE(program); - + auto tool_id = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(tool_id); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B|line|C"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|D|block||line|E"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "D"); @@ -1882,17 +1882,17 @@ TEST_F(AgentTest, should_handle_constant_values) auto agent = m_agentTestHelper->getAgent(); auto di = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(di); - + di->setConstantValue("UNAVAILABLE"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); - + "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); @@ -1906,14 +1906,14 @@ TEST_F(AgentTest, should_handle_constant_values) TEST_F(AgentTest, should_handle_bad_data_item_from_adapter) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|bad|ignore|dummy|1244|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1927,18 +1927,18 @@ TEST_F(AgentTest, adapter_should_receive_commands) { addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_ioContext.run_for(2000ms); - + m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -1946,19 +1946,19 @@ TEST_F(AgentTest, adapter_should_receive_commands) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + device->setPreserveUuid(true); m_agentTestHelper->m_adapter->parseBuffer("* uuid: XXXXXXX\n"); m_agentTestHelper->m_ioContext.run_for(1000ms); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); } - + auto &options = m_agentTestHelper->m_adapter->getOptions(); ASSERT_EQ("MK-1234", *GetOption(options, configuration::Device)); } @@ -1968,21 +1968,21 @@ TEST_F(AgentTest, adapter_should_not_process_uuid_command_with_preserve_uuid) auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.3", 4, false, true, {{configuration::PreserveUUID, true}}); addAdapter(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_TRUE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "000"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); - + "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Block", "G01X00"); @@ -1995,37 +1995,37 @@ TEST_F(AgentTest, adapter_should_receive_device_commands) { m_agentTestHelper->createAgent("/samples/two_devices.xml"); auto agent = m_agentTestHelper->getAgent(); - + auto device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); auto device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + addAdapter(); - + auto device = - GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); + GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(device1->getComponentName(), device); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-2\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device2->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: new-uuid\n"); - + device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + ASSERT_EQ("new-uuid", string(*device2->getUuid())); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-1\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device1->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); - + ASSERT_EQ("another-uuid", string(*device1->getUuid())); } @@ -2033,19 +2033,19 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) { m_agentTestHelper->createAgent("/samples/kinematics.xml", 8, 4, "1.7", 25); addAdapter(); - + auto printer = m_agentTestHelper->m_agent->getPrinter("xml"); ASSERT_FALSE(printer->getModelChangeTime().empty()); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectVersion", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->parseBuffer("* adapterVersion: 2.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectVersion: 1.7\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "2.10"); @@ -2054,24 +2054,24 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) printer->getModelChangeTime().c_str()); ; } - + // Test updating device change time string old = printer->getModelChangeTime(); m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); ASSERT_GT(printer->getModelChangeTime(), old); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@deviceModelChangeTime", printer->getModelChangeTime().c_str()); ; } - + // Test Case insensitivity - + m_agentTestHelper->m_adapter->parseBuffer("* adapterversion: 3.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectversion: 1.6\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "3.10"); @@ -2085,14 +2085,14 @@ TEST_F(AgentTest, should_handle_uuid_change) auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + addAdapter(); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -2100,11 +2100,11 @@ TEST_F(AgentTest, should_handle_uuid_change) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + auto *pipe = static_cast(m_agentTestHelper->m_adapter->getPipeline()); - + ASSERT_EQ("MK-1234", pipe->getDevice()); - + { // TODO: Fix and make sure dom is updated so this xpath will parse correctly. // PARSE_XML_RESPONSE("/current?path=//Device[@uuid=\"MK-1234\"]"); @@ -2120,7 +2120,7 @@ TEST_F(AgentTest, should_handle_uuid_change) TEST_F(AgentTest, interval_should_be_a_valid_integer_value) { QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2130,7 +2130,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) "query parameter 'interval': cannot " "convert string 'NON_INTEGER' to integer"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2138,7 +2138,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be greater than -1"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; @@ -2146,7 +2146,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be less than 2147483647"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2163,7 +2163,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2178,7 +2178,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2192,13 +2192,13 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?interval=2147483647"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", "'interval' must be less than 2147483647"); @@ -2207,7 +2207,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2231,18 +2231,18 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto rest = m_agentTestHelper->getRestService(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); rest->start(); - + // Start a thread... QueryMap query; query["interval"] = "50"; query["heartbeat"] = to_string(heartbeatFreq.count()); query["from"] = to_string(circ.getSequence()); - + // Heartbeat test. Heartbeat should be sent in 200ms. Give // 25ms range. { auto slop {35ms}; - + auto startTime = system_clock::now(); PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); while (m_agentTestHelper->m_session->m_chunkBody.empty() && @@ -2251,34 +2251,34 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto delta = system_clock::now() - startTime; cout << "Delta after heartbeat: " << delta.count() << endl; ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); EXPECT_GT((heartbeatFreq + slop), delta) - << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); + << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); EXPECT_LT(heartbeatFreq, delta) << "delta > hbf: " << delta.count(); - + m_agentTestHelper->m_session->closeStream(); } - + // Set some data and make sure we get data within 40ms. // Again, allow for some slop. { auto delay {40ms}; auto slop {35ms}; - + PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); m_agentTestHelper->m_ioContext.run_for(delay); - + auto startTime = system_clock::now(); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(5ms); auto delta = system_clock::now() - startTime; cout << "Delta after data: " << delta.count() << endl; - + ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); PARSE_XML_CHUNK(); - + auto deltaMS = duration_cast(delta); EXPECT_GT(slop, deltaMS) << "delta " << deltaMS.count() << " < delay " << slop.count(); } @@ -2290,9 +2290,9 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + /// - Set up streaming every 100ms with a 1000ms heartbeat std::map query; query["interval"] = "100"; @@ -2300,7 +2300,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) query["count"] = "10"; query["from"] = to_string(circ.getSequence()); query["path"] = "//DataItem[@name='line']"; - + /// - Test to make sure the signal will push the sequence number forward and capture /// the new data. { @@ -2312,7 +2312,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(200ms); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@sequence", seq.c_str()); } @@ -2324,9 +2324,9 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + // Start a thread... std::map query; query["interval"] = "100"; @@ -2334,14 +2334,14 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) query["count"] = "10"; query["from"] = to_string(circ.getSequence() + 5); query["path"] = "//DataItem[@name='line']"; - + // Test to make sure the signal will push the sequence number forward and capture // the new data. { PARSE_XML_RESPONSE_QUERY("/LinuxCNC/sample", query); auto seq = to_string(circ.getSequence() + 20ull); m_agentTestHelper->m_ioContext.run_for(200ms); - + ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); } } @@ -2356,18 +2356,18 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) TEST_F(AgentTest, should_allow_making_observations_via_http_put) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["line"] = "205"; queries["power"] = "ON"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@timestamp", "2021-02-01T12:00:00Z"); @@ -2380,17 +2380,17 @@ TEST_F(AgentTest, should_allow_making_observations_via_http_put) TEST_F(AgentTest, put_condition_should_parse_condition_data) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["lp"] = "FAULT|2001|1||SCANHISTORYRESET"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Fault@timestamp", "2021-02-01T12:00:00Z"); @@ -2403,7 +2403,7 @@ TEST_F(AgentTest, put_condition_should_parse_condition_data) TEST_F(AgentTest, shound_add_asset_count_when_20) { m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.0", 25); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); @@ -2423,7 +2423,7 @@ TEST_F(AgentTest, pre_start_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_TRUE(called); @@ -2439,7 +2439,7 @@ TEST_F(AgentTest, pre_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2452,7 +2452,7 @@ TEST_F(AgentTest, post_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2465,7 +2465,7 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_FALSE(called); @@ -2476,24 +2476,24 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) TEST_F(AgentTest, device_should_have_hash_for_2_2) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + auto hash = device->get("hash"); ASSERT_EQ(28, hash.length()); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@hash", hash.c_str()); } - + auto devices = m_agentTestHelper->getAgent()->getDevices(); auto di = devices.begin(); - + { PARSE_XML_RESPONSE("/Agent/sample"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[2]@hash", (*di++)->get("hash").c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[3]@hash", (*di)->get("hash").c_str()); } @@ -2502,19 +2502,19 @@ TEST_F(AgentTest, device_should_have_hash_for_2_2) TEST_F(AgentTest, should_not_add_spaces_to_output) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|program|"); m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData( - "2024-01-22T20:00:00Z|program| |block| "); - + "2024-01-22T20:00:00Z|program| |block| "); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); @@ -2531,7 +2531,7 @@ TEST_F(AgentTest, should_set_sender_from_config_in_XML_header) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); @@ -2547,12 +2547,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_validation_is_fa PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2568,12 +2568,12 @@ TEST_F(AgentTest, should_set_validation_flag_in_header_when_version_2_5_validati PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); @@ -2589,12 +2589,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2604,19 +2604,19 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ TEST_F(AgentTest, should_initialize_observaton_to_initial_value_when_available) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|avail|AVAILABLE"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "0"); diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index 3a6a513b2..9b07f7dd2 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -60,13 +60,13 @@ class MqttEntitySinkTest : public testing::Test std::shared_ptr m_client; std::unique_ptr m_jsonPrinter; uint16_t m_port {0}; - + void SetUp() override { m_agentTestHelper = std::make_unique(); m_jsonPrinter = std::make_unique(2, true); } - + void TearDown() override { stopAgent(); @@ -76,43 +76,45 @@ class MqttEntitySinkTest : public testing::Test m_agentTestHelper.reset(); m_jsonPrinter.reset(); } - + void createAgent(std::string testFile = {}, ConfigOptions options = {}) { if (testFile == "") testFile = "/samples/test_config.xml"; ConfigOptions opts(options); MergeOptions( - opts, - { - {"MqttEntitySink", true}, - {configuration::MqttPort, m_port}, - {MqttCurrentInterval, 200ms}, - {MqttSampleInterval, 100ms}, - {configuration::MqttHost, "127.0.0.1"s}, - {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, - {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, - {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, - {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, - }); + opts, + { + {"MqttEntitySink", true}, + {configuration::MqttPort, m_port}, + {MqttCurrentInterval, 200ms}, + {MqttSampleInterval, 100ms}, + {MqttSampleInterval, 100ms}, + {configuration::DisableAgentDevice, true}, + {configuration::MqttHost, "127.0.0.1"s}, + {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, + {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, + {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, + {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, + }); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 25, false, true, opts); addAdapter(); m_agentTestHelper->getAgent()->start(); } - + void createServer(const ConfigOptions& options) { using namespace mtconnect::configuration; ConfigOptions opts(options); MergeOptions(opts, {{ServerIp, "127.0.0.1"s}, - {MqttPort, 0}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); m_server = std::make_shared( - m_agentTestHelper->m_ioContext, opts); + m_agentTestHelper->m_ioContext, opts); } - + template bool waitFor(const chrono::duration& time, function pred) { @@ -125,12 +127,12 @@ class MqttEntitySinkTest : public testing::Test }); while (!timeout && !pred()) { - m_agentTestHelper->m_ioContext.run_for(100ms); + m_agentTestHelper->m_ioContext.run_for(200ms); } timer.cancel(); return pred(); } - + void startServer() { if (m_server) @@ -143,19 +145,19 @@ class MqttEntitySinkTest : public testing::Test } } } - + void createClient(const ConfigOptions& options, unique_ptr&& handler) { ConfigOptions opts(options); MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, - {MqttPort, m_port}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); + {MqttPort, m_port}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); m_client = make_shared(m_agentTestHelper->m_ioContext, opts, std::move(handler)); } - + bool startClient() { bool started = m_client && m_client->start(); @@ -165,13 +167,13 @@ class MqttEntitySinkTest : public testing::Test } return started; } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 0, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + void stopAgent() { const auto agent = m_agentTestHelper->getAgent(); @@ -181,7 +183,7 @@ class MqttEntitySinkTest : public testing::Test m_agentTestHelper->m_ioContext.run_for(100ms); } } - + void stopClient() { if (m_client) @@ -191,7 +193,7 @@ class MqttEntitySinkTest : public testing::Test m_client.reset(); } } - + void stopServer() { if (m_server) @@ -207,77 +209,85 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) { bool gotMessage = false; std::string receivedTopic; - + createServer({}); startServer(); - + auto client = mqtt::make_async_client(m_agentTestHelper->m_ioContext.get(), "localhost", m_port); - + client->set_client_id("test_client"); client->set_clean_session(true); client->set_keep_alive_sec(30); - + bool subscribed = false; client->set_connack_handler( - [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { - if (connack_return_code == mqtt::connect_return_code::accepted) - { - auto pid = client->acquire_unique_packet_id(); - client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, - [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); - } - return true; - }); - + [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { + if (connack_return_code == mqtt::connect_return_code::accepted) + { + auto pid = client->acquire_unique_packet_id(); + client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, + [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); + } + return true; + }); + client->set_suback_handler( - [&subscribed](std::uint16_t packet_id, std::vector results) { - subscribed = true; - return true; - }); - - client->set_publish_handler([&gotMessage, &receivedTopic](mqtt::optional packet_id, - mqtt::publish_options pubopts, - mqtt::buffer topic_name, - mqtt::buffer contents) { - receivedTopic = topic_name; - std::cout << "Received topic: " << topic_name << " payload: " << contents << std::endl; - if (topic_name.find("MTConnect/Devices/") == 0 && - topic_name.find("/Observations/") != std::string::npos) - { - gotMessage = true; - } - return true; - }); - + [&subscribed](std::uint16_t packet_id, std::vector results) { + subscribed = true; + return true; + }); + + size_t messageCount = 0; + client->set_publish_handler( + [&messageCount, &gotMessage, &receivedTopic](mqtt::optional packet_id, + mqtt::publish_options pubopts, + mqtt::buffer topic_name, mqtt::buffer contents) { + receivedTopic = topic_name; + std::cout << "Received topic: " << topic_name << " payload: " << contents << std::endl; + if (topic_name.find("MTConnect/Devices/") == 0 && + topic_name.find("/Observations/") != std::string::npos) + { + messageCount += 1; + gotMessage = true; + } + return true; + }); + client->async_connect([](mqtt::error_code ec) { ASSERT_FALSE(ec) << "Cannot connect"; }); - + // Wait for subscription to complete ASSERT_TRUE(waitFor(10s, [&subscribed]() { return subscribed; })) - << "Subscription never completed"; - + << "Subscription never completed"; + // Create the agent and wait for its MQTT sink to connect. createAgent(); auto sink = m_agentTestHelper->getMqttEntitySink(); ASSERT_TRUE(sink != nullptr); ASSERT_TRUE(waitFor(10s, [&sink]() { return sink->isConnected(); })) - << "MqttEntitySink failed to connect to broker"; - + << "MqttEntitySink failed to connect to broker"; + + auto device = m_agentTestHelper->m_agent->getDefaultDevice(); + size_t diCount = device->getDeviceDataItems().size(); + + // Add deterministic check to make sure we have recieved all the initial messages + ASSERT_TRUE(waitFor(5s, [&messageCount, &diCount]() { return messageCount >= diCount; })); + gotMessage = false; receivedTopic.clear(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })) - << "Timeout waiting for adapter data. Last topic: " << receivedTopic; - + << "Timeout waiting for adapter data. Last topic: " << receivedTopic; + EXPECT_TRUE(receivedTopic.find("MTConnect/Devices/") == 0) - << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; + << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; EXPECT_TRUE(receivedTopic.find("/Observations/") != std::string::npos) - << "Topic doesn't contain /Observations/: " << receivedTopic; + << "Topic doesn't contain /Observations/: " << receivedTopic; EXPECT_EQ("MTConnect/Devices/000/Observations/p3", receivedTopic) - << "Topic does not match expected format: " << receivedTopic; - + << "Topic does not match expected format: " << receivedTopic; + client->async_disconnect(); - + stopAgent(); stopClient(); } @@ -285,14 +295,14 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -310,24 +320,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); // Ensure subscription is active before sending data m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Wait a bit to ensure sink and client are ready m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify Entity JSON format EXPECT_TRUE(receivedJson.contains("dataItemId")); EXPECT_TRUE(receivedJson.contains("timestamp")); @@ -335,23 +345,23 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) EXPECT_TRUE(receivedJson.contains("sequence")); EXPECT_TRUE(receivedJson.contains("type")); EXPECT_TRUE(receivedJson.contains("category")); - + EXPECT_EQ("204", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -362,20 +372,20 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) } receivedJson = js; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify optional fields if (receivedJson.contains("name")) { @@ -385,21 +395,21 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { EXPECT_TRUE(receivedJson["subType"].is_string()); } - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotSample = false; json receivedJson; - + handler->m_receive = [&gotSample, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -418,40 +428,40 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); - + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|z2|204"); ASSERT_TRUE(waitFor(10s, [&gotSample]() { return gotSample; })); - + EXPECT_EQ("SAMPLE", receivedJson["category"].get()); EXPECT_EQ("204.000000", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotEvent = false; json receivedJson; - + handler->m_receive = [&gotEvent, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -470,39 +480,39 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|p4|READY"); ASSERT_TRUE(waitFor(10s, [&gotEvent]() { return gotEvent; })); - + EXPECT_EQ("EVENT", receivedJson["category"].get()); EXPECT_EQ("READY", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotCondition = false; json receivedJson; - + handler->m_receive = [&gotCondition, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -521,44 +531,44 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); + "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); ASSERT_TRUE(waitFor(10s, [&gotCondition]() { return gotCondition; })); - + EXPECT_EQ("CONDITION", receivedJson["category"].get()); EXPECT_EQ("FAULT", receivedJson["level"].get()); if (receivedJson.contains("nativeCode")) { EXPECT_EQ("1234", receivedJson["nativeCode"].get()); } - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotAvailable = false; std::string availabilityValue; - + handler->m_receive = [&gotAvailable, &availabilityValue](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -568,33 +578,33 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) gotAvailable = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&gotAvailable]() { return gotAvailable; })); EXPECT_EQ("AVAILABLE", availabilityValue); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); int messageCount = 0; - + handler->m_receive = [&messageCount](std::shared_ptr client, const std::string& topic, const std::string& payload) { if (topic.find("/Observations/") != std::string::npos) @@ -602,37 +612,36 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) messageCount++; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); ASSERT_TRUE(waitFor(10s, [&messageCount]() { return messageCount > 0; })); EXPECT_GT(messageCount, 0); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotUnavailable = false; json receivedJson; - + handler->m_receive = [&gotUnavailable, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - json j = json::parse(payload); if (j["category"] != "CONDITION" && topic.starts_with("MTConnect/Devices/000/Observations/")) { @@ -643,24 +652,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) receivedJson = j; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Initial observations should include UNAVAILABLE values // The issue is the initial values will be received in a random order and the conditions have a // level instead of a result. If in the interviening time a condition is received, the json may // not be correct. ASSERT_TRUE(waitFor(10s, [&gotUnavailable]() { return gotUnavailable; })); EXPECT_EQ("UNAVAILABLE", receivedJson["result"].get()); - + stopClient(); } @@ -670,23 +679,23 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_authentication) options["MqttUserName"] = "mtconnect"; options["MqttPassword"] = "password123"; options["MqttClientId"] = "auth-client"; - + createServer({}); startServer(); - + bool connected = false; auto handler = std::make_unique(); handler->m_connected = [&connected](std::shared_ptr) { connected = true; }; - + createClient(options, std::move(handler)); startClient(); - + auto start = std::chrono::steady_clock::now(); while (!connected && std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } - + ASSERT_TRUE(connected) << "MQTT client did not connect with authentication"; } @@ -695,25 +704,27 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_qos_levels) ConfigOptions options; options["MqttQOS"] = "exactly_once"; options["MqttClientId"] = "qos-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool received = false; handler->m_receive = [&received](std::shared_ptr client, const std::string& topic, const std::string& payload) { received = true; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&received]() { return received; })); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) @@ -721,10 +732,10 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) ConfigOptions options; options["MqttRetain"] = "true"; options["MqttClientId"] = "retain-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool retainedReceived = false; std::string retainedPayload; @@ -734,19 +745,19 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) retainedReceived = true; retainedPayload = payload; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&retainedReceived]() { return retainedReceived; })); ASSERT_FALSE(retainedPayload.empty()); - + stopClient(); } @@ -755,10 +766,10 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) ConfigOptions options; options["MqttLastWillTopic"] = "MTConnect/Probe/J55-411045-cpp/Availability"; options["MqttClientId"] = "lastwill-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool lastWillReceived = false; handler->m_receive = [&lastWillReceived](std::shared_ptr client, @@ -768,18 +779,18 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) lastWillReceived = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + m_client->stop(); ASSERT_TRUE(waitFor(5s, [&lastWillReceived]() { return lastWillReceived; })); - + stopClient(); } diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index 9799e9cf9..14b80eeba 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -288,36 +288,36 @@ TEST_F(ObservationValidationTest, should_validate_sample) { auto contract = static_cast(m_context->m_contract.get()); contract->m_schemaVersion = SCHEMA_VERSION(2, 5); - + shared_ptr mapper; mapper = make_shared(m_context, "", 2); mapper->bind(m_validator); - + ErrorList errors; m_dataItem = DataItem::make( - {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, - errors); - + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + auto ts = make_shared(); ts->m_tokens = {{"pos"s, "1.234"s}}; ts->m_timestamp = chrono::system_clock::now(); ts->setProperty("timestamp", ts->m_timestamp); - + auto observations = (*mapper)(ts); auto &r = *observations; ASSERT_EQ(typeid(Observations), typeid(r)); - + auto oblist = observations->getValue(); ASSERT_EQ(1, oblist.size()); - + auto oi = oblist.begin(); - + { auto sample = dynamic_pointer_cast(*oi++); ASSERT_TRUE(sample); ASSERT_EQ(m_dataItem, sample->getDataItem()); ASSERT_FALSE(sample->isUnavailable()); - + ASSERT_EQ("VALID", sample->get("quality")); } } @@ -326,17 +326,16 @@ TEST_F(ObservationValidationTest, should_validate_sample_with_int64_value) { ErrorList errors; m_dataItem = DataItem::make( - {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, - errors); - + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + auto obs = Observation::make(m_dataItem, {{"VALUE", int64_t(100)}}, m_time, errors); - + auto evt = (*m_validator)(std::move(obs)); auto quality = evt->get("quality"); ASSERT_EQ("VALID", quality); } - TEST_F(ObservationValidationTest, should_not_validate_if_validation_is_off) { auto contract = static_cast(m_context->m_contract.get()); @@ -428,7 +427,7 @@ TEST_F(ObservationValidationTest, should_validate_sample_double_value) { ErrorList errors; auto event = Observation::make(m_dataItem, {{"VALUE", "READY"s}}, m_time, errors); - + auto evt = (*m_validator)(std::move(event)); auto quality = evt->get("quality"); ASSERT_EQ("VALID", quality); From 0d4bc9c67da3adf8fbb7651f58a7e40599003240 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 15 Nov 2025 22:06:13 +0100 Subject: [PATCH 43/84] Fixed so that tests that were expecting the agent device would work --- test_package/mqtt_entity_sink_test.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index 9b07f7dd2..a6446fc2a 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -90,7 +90,6 @@ class MqttEntitySinkTest : public testing::Test {MqttCurrentInterval, 200ms}, {MqttSampleInterval, 100ms}, {MqttSampleInterval, 100ms}, - {configuration::DisableAgentDevice, true}, {configuration::MqttHost, "127.0.0.1"s}, {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, @@ -221,7 +220,7 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) bool subscribed = false; client->set_connack_handler( - [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { + [client](bool sp, mqtt::connect_return_code connack_return_code) { if (connack_return_code == mqtt::connect_return_code::accepted) { auto pid = client->acquire_unique_packet_id(); @@ -260,7 +259,7 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) << "Subscription never completed"; // Create the agent and wait for its MQTT sink to connect. - createAgent(); + createAgent("", {{configuration::DisableAgentDevice, true}}); auto sink = m_agentTestHelper->getMqttEntitySink(); ASSERT_TRUE(sink != nullptr); ASSERT_TRUE(waitFor(10s, [&sink]() { return sink->isConnected(); })) From e5b316ab84363686f4e8d969d74f77bfaaa113aa Mon Sep 17 00:00:00 2001 From: William Sobel Date: Sun, 16 Nov 2025 11:00:18 +0100 Subject: [PATCH 44/84] Update utilities.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mtconnect/utilities.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index db07add7a..1cab64242 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -267,7 +267,7 @@ namespace mtconnect { return date::format(ISO_8601_FMT, date::floor(timePoint)); case LOCAL: { - time_t t = time(nullptr); + time_t t = std::chrono::system_clock::to_time_t(timePoint); struct tm local; localtime_r(&t, &local); char buf[64]; From 7e31c1ba5aca0a7a958548071fe6d561cd877c66 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 16 Nov 2025 19:32:50 +0100 Subject: [PATCH 45/84] Cleaned up handling of localtime_x --- src/mtconnect/utilities.cpp | 4 ---- src/mtconnect/utilities.hpp | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/mtconnect/utilities.cpp b/src/mtconnect/utilities.cpp index bf33c4a5f..b9cdf7603 100644 --- a/src/mtconnect/utilities.cpp +++ b/src/mtconnect/utilities.cpp @@ -43,8 +43,6 @@ #define _WINSOCKAPI_ #include #include -#define localtime_r(t, tm) localtime_s(tm, t) -#define gmtime_r(t, tm) gmtime_s(tm, t) #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ull #endif @@ -76,8 +74,6 @@ BOOST_FUSION_ADAPT_STRUCT(mtconnect::url::Url, m_fragment)) namespace mtconnect { - AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf) { localtime_r(time, buf); } - inline string::size_type insertPrefix(string &aPath, string::size_type &aPos, const string aPrefix) { diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 1cab64242..a8515fd0c 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -238,11 +238,17 @@ namespace mtconnect { return true; } - /// @brief Gets the local time - /// @param[in] time the time - /// @param[out] buf struct tm - AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Thread safe localtime function that uses localtime_s or localtime_r based on platform + /// @param[in] timer pointer to time_t + /// @param[out] buf pointer to tm struct to fill + inline auto safe_localtime(const std::time_t* timer, std::tm* buf) { +#ifdef _WINDOWS + return localtime_s(buf, timer); +#else + return localtime_r(timer, buf); +#endif + } + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -253,9 +259,6 @@ namespace mtconnect { using namespace std; using namespace std::chrono; constexpr char ISO_8601_FMT[] = "%Y-%m-%dT%H:%M:%SZ"; -#ifdef _WINDOWS -#define localtime_r(t, tm) localtime_s(tm, t) -#endif switch (format) { @@ -269,7 +272,7 @@ namespace mtconnect { { time_t t = std::chrono::system_clock::to_time_t(timePoint); struct tm local; - localtime_r(&t, &local); + safe_localtime(&t, &local); char buf[64]; strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &local); return string(buf); From e2d0bce160e82d0b0560c6b44a2f8b11bbd581b9 Mon Sep 17 00:00:00 2001 From: William Sobel Date: Sun, 16 Nov 2025 20:22:02 +0100 Subject: [PATCH 46/84] Update mqtt_entity_sink_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test_package/mqtt_entity_sink_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index a6446fc2a..c5a4adf43 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -89,7 +89,6 @@ class MqttEntitySinkTest : public testing::Test {configuration::MqttPort, m_port}, {MqttCurrentInterval, 200ms}, {MqttSampleInterval, 100ms}, - {MqttSampleInterval, 100ms}, {configuration::MqttHost, "127.0.0.1"s}, {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, From 16d086210e6841e26968dbd24c0ad2715f561161 Mon Sep 17 00:00:00 2001 From: William Sobel Date: Sun, 16 Nov 2025 20:22:17 +0100 Subject: [PATCH 47/84] Update utilities.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mtconnect/utilities.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index a8515fd0c..dff98f5d8 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -241,11 +241,11 @@ namespace mtconnect { /// @brief Thread safe localtime function that uses localtime_s or localtime_r based on platform /// @param[in] timer pointer to time_t /// @param[out] buf pointer to tm struct to fill - inline auto safe_localtime(const std::time_t* timer, std::tm* buf) { + inline void safe_localtime(const std::time_t* timer, std::tm* buf) { #ifdef _WINDOWS - return localtime_s(buf, timer); + localtime_s(buf, timer); #else - return localtime_r(timer, buf); + localtime_r(timer, buf); #endif } From e702b37f975ad5ca55428d893094543bcba6525d Mon Sep 17 00:00:00 2001 From: William Sobel Date: Sun, 16 Nov 2025 20:22:26 +0100 Subject: [PATCH 48/84] Update mqtt_entity_sink_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test_package/mqtt_entity_sink_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index c5a4adf43..f5b781ab1 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -267,7 +267,7 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) auto device = m_agentTestHelper->m_agent->getDefaultDevice(); size_t diCount = device->getDeviceDataItems().size(); - // Add deterministic check to make sure we have recieved all the initial messages + // Add deterministic check to make sure we have received all the initial messages ASSERT_TRUE(waitFor(5s, [&messageCount, &diCount]() { return messageCount >= diCount; })); gotMessage = false; From 8046666052b03f01e381b9733b00a2526363ec4c Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 21 Nov 2025 18:56:58 +0100 Subject: [PATCH 49/84] Added target and tests --- CMakeLists.txt | 6 +- agent_lib/CMakeLists.txt | 2 + src/mtconnect/agent.cpp | 1 + src/mtconnect/asset/target.cpp | 124 +++++++++ src/mtconnect/asset/target.hpp | 65 +++++ src/mtconnect/entity/data_set.hpp | 2 +- test_package/CMakeLists.txt | 1 + test_package/target_test.cpp | 439 ++++++++++++++++++++++++++++++ 8 files changed, 636 insertions(+), 4 deletions(-) create mode 100644 src/mtconnect/asset/target.cpp create mode 100644 src/mtconnect/asset/target.hpp create mode 100644 test_package/target_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aca0d680..6c530e9cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ # The version number. set(AGENT_VERSION_MAJOR 2) -set(AGENT_VERSION_MINOR 6) +set(AGENT_VERSION_MINOR 7) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 7) -set(AGENT_VERSION_RC "") +set(AGENT_VERSION_BUILD 1) +set(AGENT_VERSION_RC "RC1") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent cmake_minimum_required(VERSION 3.23 FATAL_ERROR) diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 6c22ca22a..07d3c5db2 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -28,6 +28,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/physical_asset.hpp" "${SOURCE_DIR}/asset/fixture.hpp" "${SOURCE_DIR}/asset/pallet.hpp" + "${SOURCE_DIR}/asset/target.hpp" # src/asset SOURCE_FILES_ONLY @@ -40,6 +41,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/physical_asset.cpp" "${SOURCE_DIR}/asset/fixture.cpp" "${SOURCE_DIR}/asset/pallet.cpp" + "${SOURCE_DIR}/asset/target.cpp" # src/buffer HEADER_FILES_ONLY diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 7f27ad008..58f044562 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -102,6 +102,7 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); + m_assetStorage = make_unique( GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp new file mode 100644 index 000000000..9abb5619a --- /dev/null +++ b/src/mtconnect/asset/target.cpp @@ -0,0 +1,124 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +#include "target.hpp" + +using namespace std; + +namespace mtconnect { + using namespace entity; + namespace asset { + FactoryPtr Target::getFactory() + { + static FactoryPtr target = make_shared(); + return target; + } + + FactoryPtr Target::getDeviceTargetsFactory() + { + static FactoryPtr targets; + if (!targets) + { + targets = make_shared(entity::Requirements { + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite) + }); + + targets->registerMatchers(); + targets->setMinListSize(1); + } + + return targets; + } + + FactoryPtr Target::getTargetsFactory() + { + static FactoryPtr targets; + if (!targets) + { + targets = make_shared(entity::Requirements { + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite) + }); + + targets->registerMatchers(); + targets->setMinListSize(1); + } + + return targets; + } + + FactoryPtr TargetDevice::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + factory = make_shared(*Target::getFactory()); + factory->addRequirements({{"targetId", true}}); + } + + return factory; + } + + FactoryPtr TargetRef::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + factory = make_shared(*Target::getFactory()); + factory->addRequirements({{"groupIdRef", true}}); + } + + return factory; + } + + FactoryPtr TargetGroup::getFactory() + { + using namespace entity; + static FactoryPtr factory; + if (!factory) + { + + factory = make_shared(*Target::getFactory()); + factory->addRequirements({{"groupId", true}, + {"TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite}, + {"TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite} + }); + factory->registerMatchers(); + factory->setMinListSize(1); + } + + return factory; + } + + FactoryPtr TargetRequirement::getFactory() + { + using namespace entity; + static FactoryPtr factory; + if (!factory) + { + factory = make_shared(*Target::getFactory()); + factory->addRequirements({{"requirementId", true}, + {"CapabilityTable", ValueType::TABLE, false}}); + } + + return factory; + } + } +} // namespace mtconnect diff --git a/src/mtconnect/asset/target.hpp b/src/mtconnect/asset/target.hpp new file mode 100644 index 000000000..91e1ce9be --- /dev/null +++ b/src/mtconnect/asset/target.hpp @@ -0,0 +1,65 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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 "mtconnect/entity/entity.hpp" +#include "mtconnect/entity/factory.hpp" +#include "mtconnect/utilities.hpp" +#include "asset.hpp" + +namespace mtconnect::asset { + /// @brief abstract Physical Asset + class AGENT_LIB_API Target : public entity::Entity + { + public: + static entity::FactoryPtr getFactory(); + static entity::FactoryPtr getDeviceTargetsFactory(); + static entity::FactoryPtr getTargetsFactory(); + }; + + class AGENT_LIB_API TargetGroup : public Target + { + public: + static entity::FactoryPtr getFactory(); + }; + + class AGENT_LIB_API TargetDevice : public Target + { + public: + static entity::FactoryPtr getFactory(); + }; + + class AGENT_LIB_API TargetRef : public Target + { + public: + static entity::FactoryPtr getFactory(); + static entity::FactoryPtr getTargetsFactory(); + }; + + class AGENT_LIB_API TargetRequirement : public Target + { + public: + static entity::FactoryPtr getFactory(); + }; + + +} // namespace mtconnect::asset diff --git a/src/mtconnect/entity/data_set.hpp b/src/mtconnect/entity/data_set.hpp index 671768b19..252ca949f 100644 --- a/src/mtconnect/entity/data_set.hpp +++ b/src/mtconnect/entity/data_set.hpp @@ -130,7 +130,7 @@ namespace mtconnect::entity { }; /// @brief A set of data set entries - /// @tparam EV the entry type for the set, must have < operator. + /// @tparam ET the entry type for the set, must have < operator. template class Set : public std::set { diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 2dc65edb9..8f8caf602 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -221,6 +221,7 @@ add_agent_test(asset_hash TRUE asset) add_agent_test(physical_asset FALSE asset) add_agent_test(pallet FALSE asset) add_agent_test(fixture FALSE asset) +add_agent_test(target FALSE asset) add_agent_test(agent_device TRUE device_model) add_agent_test(component FALSE device_model) diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp new file mode 100644 index 000000000..66a0fb50c --- /dev/null +++ b/test_package/target_test.cpp @@ -0,0 +1,439 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include +#include +#include +#include +#include +#include + +#include "json_helper.hpp" +#include "mtconnect/agent.hpp" +#include "mtconnect/asset/asset.hpp" +#include "mtconnect/asset/target.hpp" +#include "mtconnect/entity/entity.hpp" +#include "mtconnect/entity/json_printer.hpp" +#include "mtconnect/entity/xml_parser.hpp" +#include "mtconnect/entity/xml_printer.hpp" +#include "mtconnect/printer//xml_printer.hpp" +#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/source/adapter/adapter.hpp" + +using json = nlohmann::json; +using namespace std; +using namespace mtconnect; +using namespace mtconnect::entity; +using namespace mtconnect::source::adapter; +using namespace mtconnect::asset; +using namespace mtconnect::printer; + +// main +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class TargetTest : public testing::Test +{ +protected: + void SetUp() override + { + m_writer = make_unique(true); + } + + void TearDown() override { m_writer.reset(); } + + std::unique_ptr m_writer; +}; + +TEST_F(TargetTest, simple_target_device) +{ + const auto doc = R"DOC( + + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + auto targets = entity->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(1, targets->size()); + auto it = targets->begin(); + + ASSERT_EQ("TargetDevice", (*it)->getName()); + ASSERT_EQ("device-1234", (*it)->get("targetId")); +} + +TEST_F(TargetTest, target_device_and_device_group) +{ + const auto doc = R"DOC( + + + + + + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + auto targets = entity->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(2, targets->size()); + auto it = targets->begin(); + + ASSERT_EQ("TargetDevice", (*it)->getName()); + ASSERT_EQ("device-1234", (*it)->get("targetId")); + + it++; + auto group = *it; + + ASSERT_EQ("TargetGroup", group->getName()); + ASSERT_EQ("group_id", group->get("groupId")); + + ASSERT_TRUE(group->hasProperty("LIST")); + const auto groupTargets = group->getListProperty(); + ASSERT_EQ(2, groupTargets.size()); + + auto git = groupTargets.begin(); + ASSERT_EQ("TargetDevice", (*git)->getName()); + ASSERT_EQ("device-5678", (*git)->get("targetId")); + + git++; + ASSERT_EQ("TargetDevice", (*git)->getName()); + ASSERT_EQ("device-9999", (*git)->get("targetId")); +} + +TEST_F(TargetTest, target_device_and_device_group_json) +{ + const auto doc = R"DOC( + + + + + + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + entity::JsonEntityPrinter jsonPrinter(2, true); + auto json = jsonPrinter.print(entity); + + ASSERT_EQ(R"JSON({ + "Root": { + "Targets": { + "TargetDevice": [ + { + "targetId": "device-1234" + } + ], + "TargetGroup": [ + { + "list": { + "TargetDevice": [ + { + "targetId": "device-5678" + }, + { + "targetId": "device-9999" + } + ] + }, + "groupId": "group_id" + } + ] + } + } +})JSON", json); +} + +TEST_F(TargetTest, nested_target_groups_with_target_refs) +{ + const auto doc = R"DOC( + + + + + + + + + + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + auto targets = entity->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(3, targets->size()); + auto it = targets->begin(); + + ASSERT_EQ("TargetDevice", (*it)->getName()); + ASSERT_EQ("device-1234", (*it)->get("targetId")); + + it++; + auto group = *it; + + ASSERT_EQ("TargetGroup", group->getName()); + ASSERT_EQ("A", group->get("groupId")); + + ASSERT_TRUE(group->hasProperty("LIST")); + const auto groupTargets = group->getListProperty(); + ASSERT_EQ(2, groupTargets.size()); + + auto git = groupTargets.begin(); + ASSERT_EQ("TargetDevice", (*git)->getName()); + ASSERT_EQ("device-5678", (*git)->get("targetId")); + + git++; + ASSERT_EQ("TargetDevice", (*git)->getName()); + ASSERT_EQ("device-9999", (*git)->get("targetId")); + + group = *(++it); + ASSERT_EQ("TargetGroup", group->getName()); + ASSERT_EQ("B", group->get("groupId")); + + ASSERT_TRUE(group->hasProperty("LIST")); + const auto groupTargets2 = group->getListProperty(); + ASSERT_EQ(2, groupTargets2.size()); + + git = groupTargets2.begin(); + ASSERT_EQ("TargetDevice", (*git)->getName()); + ASSERT_EQ("device-2222", (*git)->get("targetId")); + + git++; + ASSERT_EQ("TargetRef", (*git)->getName()); + ASSERT_EQ("A", (*git)->get("groupIdRef")); +} + +TEST_F(TargetTest, reject_empty_groups) +{ + using namespace date; + const auto doc = R"DOC( + + + + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(2, errors.size()); + ASSERT_TRUE(entity); + + auto targets = entity->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(1, targets->size()); + auto it = targets->begin(); + + ASSERT_EQ("TargetDevice", (*it)->getName()); + ASSERT_EQ("device-1234", (*it)->get("targetId")); +} + +TEST_F(TargetTest, verify_target_requirement) +{ + const auto doc = R"DOC( + + + + + ABC + 123 + + + + +)DOC"; + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + auto targets = entity->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(1, targets->size()); + auto it = targets->begin(); + + ASSERT_EQ("TargetRequirement", (*it)->getName()); + ASSERT_EQ("req1", (*it)->get("requirementId")); + + ASSERT_TRUE((*it)->hasProperty("CapabilityTable")); + const auto table = (*it)->get("CapabilityTable"); + + ASSERT_EQ(2, table.size()); + + auto rowIt = table.begin(); + ASSERT_EQ("R1", rowIt->m_key); + ASSERT_TRUE(holds_alternative(rowIt->m_value)); + auto &row = get(rowIt->m_value); + ASSERT_EQ(1, row.size()); + + auto cellIt = row.begin(); + ASSERT_EQ("C1", cellIt->m_key); + ASSERT_TRUE(holds_alternative(cellIt->m_value)); + ASSERT_EQ("ABC", get(cellIt->m_value)); + + rowIt++; + ASSERT_EQ("R2", rowIt->m_key); + ASSERT_TRUE(holds_alternative(rowIt->m_value)); + auto &row2 = get(rowIt->m_value); + ASSERT_EQ(1, row2.size()); + + cellIt = row2.begin(); + ASSERT_EQ("C2", cellIt->m_key); + ASSERT_TRUE(holds_alternative(cellIt->m_value)); + ASSERT_EQ(123, get(cellIt->m_value)); +} + +TEST_F(TargetTest, verify_target_requirement_in_json) +{ + const auto doc = R"DOC( + + + + + ABC + 123 + + + + +)DOC"; + + + auto root = make_shared(); + auto tf = make_shared(Requirements { + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false} + }); + root->registerFactory("Root", tf); + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(root, doc, errors); + ASSERT_EQ(0, errors.size()); + ASSERT_TRUE(entity); + + entity::JsonEntityPrinter jsonPrinter(2, true); + auto json = jsonPrinter.print(entity); + + ASSERT_EQ(R"JSON({ + "Root": { + "Targets": { + "TargetRequirement": [ + { + "CapabilityTable": { + "R1": { + "C1": "ABC" + }, + "R2": { + "C2": 123 + } + }, + "requirementId": "req1" + } + ] + } + } +})JSON", json); + +} From d1385e48c40ba0ebfd6532110a3cd9551aaf9d1f Mon Sep 17 00:00:00 2001 From: Dave Wickelhaus Date: Sun, 23 Nov 2025 21:41:55 -0500 Subject: [PATCH 50/84] On branch main_websocket_fixes_3 Changes to be committed: modified: src/mtconnect/sink/rest_sink/rest_service.cpp - RestService::createAssetRoutings - added asset, assets commands to the handler routings - added assetsById command to to the idHandler routings - RestService::createSampleRoutings - added code to set the request->parameter("count") parameter to 100 if it's not instantiated. --- src/mtconnect/sink/rest_sink/rest_service.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 1f01a8445..d550ed06e 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -569,12 +569,14 @@ namespace mtconnect { "count={integer:100}&device={string}&pretty={bool:false}&format={string}"); m_server->addRouting({boost::beast::http::verb::get, "/assets?" + qp, handler}) .document("MTConnect assets request", "Returns up to `count` assets"); + m_server->addRouting({boost::beast::http::verb::get, "/{device}/assets?" + qp, handler}) + .document("MTConnect assets request", "Returns up to `count` assets for deivce `device`") + .command("assets"); m_server->addRouting({boost::beast::http::verb::get, "/asset?" + qp, handler}) .document("MTConnect asset request", "Returns up to `count` assets"); - m_server->addRouting({boost::beast::http::verb::get, "/{device}/assets?" + qp, handler}) - .document("MTConnect assets request", "Returns up to `count` assets for deivce `device`"); m_server->addRouting({boost::beast::http::verb::get, "/{device}/asset?" + qp, handler}) - .document("MTConnect asset request", "Returns up to `count` assets for deivce `device`"); + .document("MTConnect asset request", "Returns up to `count` assets for deivce `device`") + .command("asset"); m_server->addRouting({boost::beast::http::verb::get, "/assets/{assetIds}", idHandler}) .document( "MTConnect assets request", @@ -582,7 +584,8 @@ namespace mtconnect { m_server->addRouting({boost::beast::http::verb::get, "/asset/{assetIds}", idHandler}) .document("MTConnect asset request", "Returns a set of assets identified by asset ids `asset` separated by " - "semi-colon (;)"); + "semi-colon (;)") + .command("assetsById"); if (m_server->arePutsAllowed()) { @@ -727,9 +730,13 @@ namespace mtconnect { void RestService::createSampleRoutings() { using namespace rest_sink; + + auto handler = [&](SessionPtr session, RequestPtr request) -> bool { request->m_request = "MTConnectStreams"; + if(!request->parameter("count")) request->m_parameters["count"] =100; + auto interval = request->parameter("interval"); if (interval) { From 17e5267266cb8f0f6a1ce06d276b7ee39aa33adc Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 25 Nov 2025 15:15:08 +0100 Subject: [PATCH 51/84] Added initial process archetype asset tests --- agent_lib/CMakeLists.txt | 2 + src/mtconnect/agent.cpp | 3 +- src/mtconnect/asset/asset.cpp | 5 +- src/mtconnect/asset/process.cpp | 115 ++++++ src/mtconnect/asset/process.hpp | 45 +++ src/mtconnect/asset/target.cpp | 36 ++ src/mtconnect/asset/target.hpp | 9 +- test_package/CMakeLists.txt | 1 + test_package/process_test.cpp | 648 ++++++++++++++++++++++++++++++++ test_package/target_test.cpp | 4 +- 10 files changed, 862 insertions(+), 6 deletions(-) create mode 100644 src/mtconnect/asset/process.cpp create mode 100644 src/mtconnect/asset/process.hpp create mode 100644 test_package/process_test.cpp diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 07d3c5db2..9148d5d8f 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -27,6 +27,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/component_configuration_parameters.hpp" "${SOURCE_DIR}/asset/physical_asset.hpp" "${SOURCE_DIR}/asset/fixture.hpp" + "${SOURCE_DIR}/asset/process.hpp" "${SOURCE_DIR}/asset/pallet.hpp" "${SOURCE_DIR}/asset/target.hpp" @@ -40,6 +41,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/component_configuration_parameters.cpp" "${SOURCE_DIR}/asset/physical_asset.cpp" "${SOURCE_DIR}/asset/fixture.cpp" + "${SOURCE_DIR}/asset/process.cpp" "${SOURCE_DIR}/asset/pallet.cpp" "${SOURCE_DIR}/asset/target.cpp" diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 58f044562..bcaadba45 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -48,6 +48,7 @@ #include "mtconnect/asset/file_asset.hpp" #include "mtconnect/asset/fixture.hpp" #include "mtconnect/asset/pallet.hpp" +#include "mtconnect/asset/process.hpp" #include "mtconnect/asset/qif_document.hpp" #include "mtconnect/asset/raw_material.hpp" #include "mtconnect/configuration/config_options.hpp" @@ -102,7 +103,7 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); - + Process::registerAsset(); m_assetStorage = make_unique( GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); diff --git a/src/mtconnect/asset/asset.cpp b/src/mtconnect/asset/asset.cpp index 92422d010..cb33e24c8 100644 --- a/src/mtconnect/asset/asset.cpp +++ b/src/mtconnect/asset/asset.cpp @@ -15,7 +15,8 @@ // limitations under the License. // -#include "mtconnect/asset/asset.hpp" +#include "asset.hpp" +#include "mtconnect/device_model/configuration/configuration.hpp" #include #include @@ -27,10 +28,12 @@ namespace mtconnect { namespace asset { FactoryPtr Asset::getFactory() { + using namespace device_model::configuration; static auto asset = make_shared( Requirements({Requirement("assetId", false), Requirement("deviceUuid", false), Requirement("timestamp", ValueType::TIMESTAMP, false), Requirement("hash", false), + Requirement("Configuration", ValueType::ENTITY, Configuration::getFactory(), false), Requirement("removed", ValueType::BOOL, false)}), [](const std::string &name, Properties &props) -> EntityPtr { return make_shared(name, props); diff --git a/src/mtconnect/asset/process.cpp b/src/mtconnect/asset/process.cpp new file mode 100644 index 000000000..4b3aebc8e --- /dev/null +++ b/src/mtconnect/asset/process.cpp @@ -0,0 +1,115 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +#include "process.hpp" +#include "target.hpp" + +using namespace std; + +namespace mtconnect { + using namespace entity; + namespace asset { + FactoryPtr ProcessArchetype::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + auto activity = make_shared(Requirements { + {"sequence", ValueType::INTEGER, false}, + {"activityId", true}, + {"precedence", ValueType::INTEGER, false}, + {"optional", ValueType::BOOL, false}, + {"Description", false} + }); + + auto activityGroup = make_shared(Requirements { + {"activityGroupId", true}, + {"name", false}, + {"Activity", ValueType::ENTITY, activity, 1, entity::Requirement::Infinite}, + }); + + auto activityGroups = make_shared(Requirements { + {"ActivityGroup", ValueType::ENTITY, activityGroup, 1, entity::Requirement::Infinite} + }); + + auto processStep = make_shared(Requirements {{ + {"stepId", true}, + {"optional", ValueType::BOOL, false}, + {"sequence", ValueType::INTEGER, false}, + {"Description", false}, + {"StartTime", ValueType::TIMESTAMP, false}, + {"Duration", ValueType::DOUBLE, false}, + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, + {"ActivityGroups", ValueType::ENTITY_LIST, activityGroups, false} + }}); + processStep->setOrder({"Description", "StartTime", "Duration", "Targets", "ActivityGroups"}); + + auto routing = make_shared(Requirements { + {"precedence", ValueType::INTEGER, true}, + {"routingId", true}, + {"ProcessStep", ValueType::ENTITY, processStep, 1, entity::Requirement::Infinite}, + }); + + auto routings = make_shared(Requirements { + {"Routing", ValueType::ENTITY, routing, 1, entity::Requirement::Infinite} + }); + + factory = make_shared(*Asset::getFactory()); + factory->addRequirements({ + {"revision", true}, + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, + {"Routings", ValueType::ENTITY_LIST, routings, true}, + }); + factory->setOrder({"Configuration", "Routings", "Targets"}); + } + return factory; + } + + void ProcessArchetype::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("ProcessArchetype", getFactory()); + once = false; + } + } + + FactoryPtr Process::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + factory = ProcessArchetype::getFactory()->deepCopy(); + auto routings = factory->getRequirement("Routings"); + auto routing = routings->getFactory()->getRequirement("Routing"); + routing->setMultiplicity(1, 1); + } + return factory; + } + + void Process::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("Process", getFactory()); + once = false; + } + } + } // namespace asset +} // namespace mtconnect diff --git a/src/mtconnect/asset/process.hpp b/src/mtconnect/asset/process.hpp new file mode 100644 index 000000000..3944036a7 --- /dev/null +++ b/src/mtconnect/asset/process.hpp @@ -0,0 +1,45 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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 "asset.hpp" +#include "mtconnect/entity/factory.hpp" +#include "mtconnect/utilities.hpp" +#include "asset.hpp" + +namespace mtconnect::asset { + /// @brief Manufacturing process archetype asset + class AGENT_LIB_API ProcessArchetype : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; + + /// @brief Manufacturing process asset + class AGENT_LIB_API Process : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; +} // namespace mtconnect::asset diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp index 9abb5619a..c1d12e2d1 100644 --- a/src/mtconnect/asset/target.cpp +++ b/src/mtconnect/asset/target.cpp @@ -46,6 +46,24 @@ namespace mtconnect { } FactoryPtr Target::getTargetsFactory() + { + static FactoryPtr targets; + if (!targets) + { + targets = make_shared(entity::Requirements { + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite), + entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite) + }); + + targets->registerMatchers(); + targets->setMinListSize(1); + } + + return targets; + } + + FactoryPtr Target::getAllTargetsFactory() { static FactoryPtr targets; if (!targets) @@ -64,6 +82,24 @@ namespace mtconnect { return targets; } + + FactoryPtr Target::getRequirementTargetsFactory() + { + static FactoryPtr targets; + if (!targets) + { + targets = make_shared(entity::Requirements { + entity::Requirement("TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite) + }); + + targets->registerMatchers(); + targets->setMinListSize(1); + } + + return targets; + } + + FactoryPtr TargetDevice::getFactory() { static FactoryPtr factory; diff --git a/src/mtconnect/asset/target.hpp b/src/mtconnect/asset/target.hpp index 91e1ce9be..cb6b8ed1d 100644 --- a/src/mtconnect/asset/target.hpp +++ b/src/mtconnect/asset/target.hpp @@ -27,34 +27,39 @@ #include "asset.hpp" namespace mtconnect::asset { - /// @brief abstract Physical Asset + /// @brief A target of a process or a task class AGENT_LIB_API Target : public entity::Entity { public: static entity::FactoryPtr getFactory(); static entity::FactoryPtr getDeviceTargetsFactory(); static entity::FactoryPtr getTargetsFactory(); + static entity::FactoryPtr getAllTargetsFactory(); + static entity::FactoryPtr getRequirementTargetsFactory(); }; + /// @brief A group of possible targets class AGENT_LIB_API TargetGroup : public Target { public: static entity::FactoryPtr getFactory(); }; + /// @brief A device target where the `targetId` is the device UUID class AGENT_LIB_API TargetDevice : public Target { public: static entity::FactoryPtr getFactory(); }; + /// @brief A reference to a target group class AGENT_LIB_API TargetRef : public Target { public: static entity::FactoryPtr getFactory(); - static entity::FactoryPtr getTargetsFactory(); }; + /// @brief A requirement for a target class AGENT_LIB_API TargetRequirement : public Target { public: diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 8f8caf602..3d9cda681 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -222,6 +222,7 @@ add_agent_test(physical_asset FALSE asset) add_agent_test(pallet FALSE asset) add_agent_test(fixture FALSE asset) add_agent_test(target FALSE asset) +add_agent_test(process FALSE asset) add_agent_test(agent_device TRUE device_model) add_agent_test(component FALSE device_model) diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp new file mode 100644 index 000000000..e8933b0f3 --- /dev/null +++ b/test_package/process_test.cpp @@ -0,0 +1,648 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include +#include +#include +#include +#include + +#include "agent_test_helper.hpp" +#include "json_helper.hpp" +#include "mtconnect/agent.hpp" +#include "mtconnect/asset/asset.hpp" +#include "mtconnect/asset/process.hpp" +#include "mtconnect/entity/xml_parser.hpp" +#include "mtconnect/entity/xml_printer.hpp" +#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/source/adapter/adapter.hpp" + +using json = nlohmann::json; +using namespace std; +using namespace mtconnect; +using namespace mtconnect::entity; +using namespace mtconnect::source::adapter; +using namespace mtconnect::asset; +using namespace mtconnect::printer; + +// main +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class ProcessAssetTest : public testing::Test +{ +protected: + void SetUp() override + { // Create an agent with only 16 slots and 8 data items. + ProcessArchetype::registerAsset(); + Process::registerAsset(); + m_writer = make_unique(true); + } + + void TearDown() override { m_writer.reset(); } + + std::unique_ptr m_writer; + std::unique_ptr m_agentTestHelper; +}; + +TEST_F(ProcessAssetTest, should_parse_a_process_archetype) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + + + First Activity + + + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + ASSERT_EQ("ProcessArchetype", asset->getName()); + ASSERT_EQ("PROCESS_ARCH_ID", asset->getAssetId()); + ASSERT_EQ("1", asset->get("revision")); + + auto configuration = asset->get("Configuration"); + ASSERT_TRUE(configuration); + + auto relationships = configuration->getList("Relationships"); + ASSERT_TRUE(relationships); + ASSERT_EQ(1, relationships->size()); + + { + auto it = relationships->begin(); + ASSERT_EQ("reference_id", (*it)->get("id")); + ASSERT_EQ("PART_ID", (*it)->get("assetIdRef")); + ASSERT_EQ("PEER", (*it)->get("type")); + ASSERT_EQ("PART_ARCHETYPE", (*it)->get("assetType")); + } + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(1, routings->size()); + + { + auto routing = routings->front(); + ASSERT_EQ("routng1", routing->get("routingId")); + ASSERT_EQ(1, routing->get("precedence")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + + auto step = processSteps.front(); + ASSERT_EQ("10", step->get("stepId")); + ASSERT_EQ("Process Step 10", step->get("Description")); + + auto st = step->get("StartTime"); + ASSERT_EQ("2025-11-24T00:00:00Z", getCurrentTime(st, GMT)); + ASSERT_EQ(23000, step->get("Duration")); + + auto targets = step->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(1, targets->size()); + + { + auto it = targets->begin(); + ASSERT_EQ("group1", (*it)->get("groupIdRef")); + } + + auto activityGroups = step->getList("ActivityGroups"); + ASSERT_TRUE(activityGroups); + ASSERT_EQ(1, activityGroups->size()); + + { + auto it = activityGroups->begin(); + auto activityGroup = *it; + ASSERT_EQ("act1", activityGroup->get("activityGroupId")); + + auto activities = activityGroup->get("Activity"); + ASSERT_EQ(1, activities.size()); + + auto activity = activities.front(); + ASSERT_EQ("a1", activity->get("activityId")); + ASSERT_EQ(1, activity->get("sequence")); + ASSERT_EQ("First Activity", activity->get("Description")); + } + } + + auto targets = asset->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(2, targets->size()); + + { + auto it = targets->begin(); + ASSERT_EQ("TargetDevice", (*it)->getName()); + ASSERT_EQ("device1", (*it)->get("targetId")); + + it++; + auto targetGroup = *it; + ASSERT_EQ("TargetGroup", targetGroup->getName()); + ASSERT_EQ("group1", targetGroup->get("groupId")); + + auto targetDevices = targetGroup->get("LIST"); + ASSERT_EQ(2, targetDevices.size()); + + { + auto dit = targetDevices.begin(); + ASSERT_EQ("TargetDevice", (*dit)->getName()); + ASSERT_EQ("device2", (*dit)->get("targetId")); + dit++; + ASSERT_EQ("TargetDevice", (*dit)->getName()); + ASSERT_EQ("device3", (*dit)->get("targetId")); + } + } + + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(ProcessAssetTest, process_archetype_can_have_multiple_routings) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + Process Step 11 + 2025-11-25T00:00:00Z + 20000 + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(2, routings->size()); + + auto it = routings->begin(); + { + auto routing = *it; + ASSERT_EQ("routng1", routing->get("routingId")); + ASSERT_EQ(1, routing->get("precedence")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + + auto step = processSteps.front(); + ASSERT_EQ("10", step->get("stepId")); + ASSERT_EQ("Process Step 10", step->get("Description")); + + auto st = step->get("StartTime"); + ASSERT_EQ("2025-11-24T00:00:00Z", getCurrentTime(st, GMT)); + ASSERT_EQ(23000, step->get("Duration")); + } + it++; + { + auto routing = *it; + ASSERT_EQ("routng2", routing->get("routingId")); + ASSERT_EQ(2, routing->get("precedence")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + + auto step = processSteps.front(); + ASSERT_EQ("11", step->get("stepId")); + ASSERT_EQ("Process Step 11", step->get("Description")); + + auto st = step->get("StartTime"); + ASSERT_EQ("2025-11-25T00:00:00Z", getCurrentTime(st, GMT)); + ASSERT_EQ(20000, step->get("Duration")); + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(ProcessAssetTest, process_steps_can_be_optional) +{ + const auto doc = + R"DOC( + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(1, routings->size()); + + auto routing = routings->front(); + ASSERT_EQ("routng1", routing->get("routingId")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + auto step = processSteps.front(); + + ASSERT_EQ("10", step->get("stepId")); + ASSERT_EQ(5, step->get("sequence")); + ASSERT_EQ(true, step->get("optional")); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(ProcessAssetTest, process_archetype_must_have_at_least_one_routing) +{ + const auto doc = + R"DOC( + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + auto error = dynamic_cast(errors.front().get()); + + ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + ASSERT_EQ("ProcessArchetype", error->getEntity()); + ASSERT_EQ("Routings", error->getProperty()); +} + +TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) +{ + const auto doc = + R"DOC( + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(5, errors.size()); + + auto it = errors.begin(); + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, error->what()); + EXPECT_EQ("Routing", error->getEntity()); + EXPECT_EQ("ProcessStep", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("Routings: Invalid element 'Routing'"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 found"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + EXPECT_EQ("Routing", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("ProcessArchetype: Invalid element 'Routings'"s, error->what()); + EXPECT_EQ("ProcessArchetype", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + EXPECT_EQ("ProcessArchetype", error->getEntity()); + EXPECT_EQ("Routings", error->getProperty()); + } + +} + +TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) +{ + const auto doc = + R"DOC( + + + + + + + First Activity + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(1, routings->size()); + + auto routing = routings->front(); + ASSERT_EQ("routng1", routing->get("routingId")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + auto step = processSteps.front(); + + auto activityGroups = step->getList("ActivityGroups"); + ASSERT_TRUE(activityGroups); + ASSERT_EQ(1, activityGroups->size()); + + auto activityGroup = activityGroups->front(); + ASSERT_EQ("act1", activityGroup->get("activityGroupId")); + ASSERT_EQ("fred", activityGroup->get("name")); + + auto activities = activityGroup->get("Activity"); + ASSERT_EQ(1, activities.size()); + + auto activity = activities.front(); + ASSERT_EQ("a1", activity->get("activityId")); + ASSERT_EQ(2, activity->get("sequence")); + ASSERT_EQ("First Activity", activity->get("Description")); + ASSERT_EQ(true, activity->get("optional")); + ASSERT_EQ(3, activity->get("precedence")); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(ProcessAssetTest, should_generate_json) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + + + First Activity + + + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + entity::JsonEntityPrinter jprinter(2, true); + + auto sdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "ProcessArchetype": { + "Configuration": { + "Relationships": { + "AssetRelationship": [ + { + "assetIdRef": "PART_ID", + "assetType": "PART_ARCHETYPE", + "id": "reference_id", + "type": "PEER" + } + ] + } + }, + "Routings": { + "Routing": [ + { + "ProcessStep": [ + { + "ActivityGroups": { + "ActivityGroup": [ + { + "Activity": [ + { + "Description": "First Activity", + "activityId": "a1", + "optional": true, + "precedence": 2, + "sequence": 1 + } + ], + "activityGroupId": "act1", + "name": "fred" + } + ] + }, + "Description": "Process Step 10", + "Duration": 23000.0, + "StartTime": "2025-11-24T00:00:00Z", + "Targets": { + "TargetRef": [ + { + "groupIdRef": "group1" + } + ] + }, + "stepId": "10" + } + ], + "precedence": 1, + "routingId": "routng1" + } + ] + }, + "Targets": { + "TargetDevice": [ + { + "targetId": "device1" + } + ], + "TargetGroup": [ + { + "list": { + "TargetDevice": [ + { + "targetId": "device2" + }, + { + "targetId": "device3" + } + ] + }, + "groupId": "group1" + } + ] + }, + "assetId": "PROCESS_ARCH_ID", + "revision": "1" + } + +)", sdoc); +} diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index 66a0fb50c..75cfb2080 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -337,7 +337,7 @@ TEST_F(TargetTest, verify_target_requirement) auto root = make_shared(); auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false} + {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false} }); root->registerFactory("Root", tf); @@ -402,7 +402,7 @@ TEST_F(TargetTest, verify_target_requirement_in_json) auto root = make_shared(); auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false} + {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false} }); root->registerFactory("Root", tf); From 1ddd85f2456b80f653d0f8d0a5495238b7ebcfc5 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 26 Nov 2025 15:48:34 +0100 Subject: [PATCH 52/84] Fixed json v2 printing of lists with attributes, no need for 'list' object embedded --- src/mtconnect/entity/json_printer.hpp | 19 +++++++++++++------ test_package/json_printer_test.cpp | 4 ++-- test_package/process_test.cpp | 21 +++++++++------------ test_package/target_test.cpp | 18 ++++++++---------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/mtconnect/entity/json_printer.hpp b/src/mtconnect/entity/json_printer.hpp index 47e64f648..3dc3216c9 100644 --- a/src/mtconnect/entity/json_printer.hpp +++ b/src/mtconnect/entity/json_printer.hpp @@ -47,8 +47,15 @@ namespace mtconnect::entity { bool isPropertyList = *m_key != "LIST"; if (m_entity->hasListWithAttribute()) { - m_obj->Key("list"); - m_printer.printEntityList(arg); + if (m_printer.m_version == 1) + { + m_obj->Key("list"); + m_printer.printEntityList(arg); + } + else + { + m_printer.printEntityList(arg, true); + } } else if (isPropertyList) { @@ -147,12 +154,12 @@ namespace mtconnect::entity { /// @param[in] list a list of EntityPtr objects /// @tparam T2 Type of iterable collection must contain Entity subclass template - void printEntityList(const T2 &list) + void printEntityList(const T2 &list, bool embed = false) { if (m_version == 1) printEntityList1(list); else if (m_version == 2) - printEntityList2(list); + printEntityList2(list, embed); else throw std::runtime_error("Invalid json printer version"); } @@ -184,9 +191,9 @@ namespace mtconnect::entity { /// @param[in] list a list of EntityPtr objects /// @tparam T2 Type of iterable collection must contain Entity subclass template - void printEntityList2(const T2 &list) + void printEntityList2(const T2 &list, bool embed = false) { - AutoJsonObject obj(m_writer); + AutoJsonObject obj(m_writer, !embed); // Sort the entities by name, use a string view so we don't copy std::multimap entities; for (auto &e : list) diff --git a/test_package/json_printer_test.cpp b/test_package/json_printer_test.cpp index 0405d6ea2..9bbcf0789 100644 --- a/test_package/json_printer_test.cpp +++ b/test_package/json_printer_test.cpp @@ -357,9 +357,9 @@ TEST_F(JsonPrinterTest, elements_with_property_list_version_2) ASSERT_EQ(2, jdoc.at("/Root/CuttingItems/count"_json_pointer).get()); ASSERT_EQ("1", - jdoc.at("/Root/CuttingItems/list/CuttingItem/0/itemId"_json_pointer).get()); + jdoc.at("/Root/CuttingItems/CuttingItem/0/itemId"_json_pointer).get()); ASSERT_EQ("2", - jdoc.at("/Root/CuttingItems/list/CuttingItem/1/itemId"_json_pointer).get()); + jdoc.at("/Root/CuttingItems/CuttingItem/1/itemId"_json_pointer).get()); } TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp index e8933b0f3..d7dcd92c2 100644 --- a/test_package/process_test.cpp +++ b/test_package/process_test.cpp @@ -626,16 +626,14 @@ TEST_F(ProcessAssetTest, should_generate_json) ], "TargetGroup": [ { - "list": { - "TargetDevice": [ - { - "targetId": "device2" - }, - { - "targetId": "device3" - } - ] - }, + "TargetDevice": [ + { + "targetId": "device2" + }, + { + "targetId": "device3" + } + ], "groupId": "group1" } ] @@ -643,6 +641,5 @@ TEST_F(ProcessAssetTest, should_generate_json) "assetId": "PROCESS_ARCH_ID", "revision": "1" } - -)", sdoc); +})", sdoc); } diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index 75cfb2080..694afbbc1 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -193,16 +193,14 @@ TEST_F(TargetTest, target_device_and_device_group_json) ], "TargetGroup": [ { - "list": { - "TargetDevice": [ - { - "targetId": "device-5678" - }, - { - "targetId": "device-9999" - } - ] - }, + "TargetDevice": [ + { + "targetId": "device-5678" + }, + { + "targetId": "device-9999" + } + ], "groupId": "group_id" } ] From dfeb8a08de7a39bd7c2c9e42808dc253377fa92c Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 26 Nov 2025 16:04:53 +0100 Subject: [PATCH 53/84] Added process tests --- test_package/process_test.cpp | 120 ++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp index d7dcd92c2..c9a53c56e 100644 --- a/test_package/process_test.cpp +++ b/test_package/process_test.cpp @@ -643,3 +643,123 @@ TEST_F(ProcessAssetTest, should_generate_json) } })", sdoc); } + +TEST_F(ProcessAssetTest, should_parse_and_generate_a_process) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + + + First Activity + + + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(ProcessAssetTest, process_can_only_have_one_routings) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + Process Step 11 + 2025-11-25T00:00:00Z + 20000 + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + auto it = errors.begin(); + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more than 1 entries, 2 found"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + EXPECT_EQ("Routing", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("Process: Invalid element 'Routings'"s, error->what()); + EXPECT_EQ("Process", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Process(Routings): Property Routings is required and not provided"s, error->what()); + EXPECT_EQ("Process", error->getEntity()); + EXPECT_EQ("Routings", error->getProperty()); + } +} From b454c911f12527209710666a1f45e324c901cb75 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 26 Nov 2025 18:06:29 +0100 Subject: [PATCH 54/84] Initial version of part models and tests --- agent_lib/CMakeLists.txt | 2 + src/mtconnect/agent.cpp | 4 + src/mtconnect/asset/part.cpp | 103 ++++++ src/mtconnect/asset/part.hpp | 45 +++ src/mtconnect/asset/target.cpp | 2 +- test_package/CMakeLists.txt | 1 + test_package/part_test.cpp | 657 +++++++++++++++++++++++++++++++++ test_package/process_test.cpp | 54 +-- test_package/target_test.cpp | 48 +-- 9 files changed, 864 insertions(+), 52 deletions(-) create mode 100644 src/mtconnect/asset/part.cpp create mode 100644 src/mtconnect/asset/part.hpp create mode 100644 test_package/part_test.cpp diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 9148d5d8f..ade1e8807 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -27,6 +27,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/component_configuration_parameters.hpp" "${SOURCE_DIR}/asset/physical_asset.hpp" "${SOURCE_DIR}/asset/fixture.hpp" + "${SOURCE_DIR}/asset/part.hpp" "${SOURCE_DIR}/asset/process.hpp" "${SOURCE_DIR}/asset/pallet.hpp" "${SOURCE_DIR}/asset/target.hpp" @@ -41,6 +42,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/component_configuration_parameters.cpp" "${SOURCE_DIR}/asset/physical_asset.cpp" "${SOURCE_DIR}/asset/fixture.cpp" + "${SOURCE_DIR}/asset/part.cpp" "${SOURCE_DIR}/asset/process.cpp" "${SOURCE_DIR}/asset/pallet.cpp" "${SOURCE_DIR}/asset/target.cpp" diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index bcaadba45..5371cf58a 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -49,6 +49,7 @@ #include "mtconnect/asset/fixture.hpp" #include "mtconnect/asset/pallet.hpp" #include "mtconnect/asset/process.hpp" +#include "mtconnect/asset/part.hpp" #include "mtconnect/asset/qif_document.hpp" #include "mtconnect/asset/raw_material.hpp" #include "mtconnect/configuration/config_options.hpp" @@ -104,6 +105,9 @@ namespace mtconnect { Pallet::registerAsset(); Fixture::registerAsset(); Process::registerAsset(); + ProcessArchetype::registerAsset(); + Part::registerAsset(); + PartArchetype::registerAsset(); m_assetStorage = make_unique( GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); diff --git a/src/mtconnect/asset/part.cpp b/src/mtconnect/asset/part.cpp new file mode 100644 index 000000000..abe65c222 --- /dev/null +++ b/src/mtconnect/asset/part.cpp @@ -0,0 +1,103 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +#include "part.hpp" + +using namespace std; + +namespace mtconnect { + using namespace entity; + namespace asset { + FactoryPtr PartArchetype::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + auto customer = make_shared(Requirements { + {"customerId", true}, + {"name", false}, + {"Address", false}, + {"Description", false}, + }); + + auto customers = make_shared(Requirements { + {"Customer", ValueType::ENTITY, customer, 1, entity::Requirement::Infinite} + }); + + factory = make_shared(*Asset::getFactory()); + factory->addRequirements({ + {"revision", true}, + {"family", false}, + {"drawing", false}, + {"Customers", ValueType::ENTITY_LIST, customers, true}, + }); + } + return factory; + } + + void PartArchetype::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("PartArchetype", getFactory()); + once = false; + } + } + + FactoryPtr Part::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + auto identifier = make_shared(Requirements { + {"type", true}, + {"stepIdRef", false}, + {"timestamp", ValueType::TIMESTAMP, true} + }); + + + auto identifiers = make_shared(Requirements { + {"UniqueIdentitier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}, + {"GroupIdentifier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite} + }); + + identifiers->setMinListSize(1); + identifiers->registerMatchers(); + + factory = make_shared(*Asset::getFactory()); + factory->addRequirements({ + {"revision", true}, + {"family", false}, + {"drawing", false}, + {"PartIdentifiers", ValueType::ENTITY_LIST, identifiers, false} + }); + } + return factory; + } + + void Part::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("Part", getFactory()); + once = false; + } + } + } // namespace asset +} // namespace mtconnect diff --git a/src/mtconnect/asset/part.hpp b/src/mtconnect/asset/part.hpp new file mode 100644 index 000000000..2f5acbaba --- /dev/null +++ b/src/mtconnect/asset/part.hpp @@ -0,0 +1,45 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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 "asset.hpp" +#include "mtconnect/entity/factory.hpp" +#include "mtconnect/utilities.hpp" +#include "asset.hpp" + +namespace mtconnect::asset { + /// @brief Manufacturing process archetype asset + class AGENT_LIB_API PartArchetype : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; + + /// @brief Manufacturing process asset + class AGENT_LIB_API Part : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; +} // namespace mtconnect::asset diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp index c1d12e2d1..f165bc88b 100644 --- a/src/mtconnect/asset/target.cpp +++ b/src/mtconnect/asset/target.cpp @@ -106,7 +106,7 @@ namespace mtconnect { if (!factory) { factory = make_shared(*Target::getFactory()); - factory->addRequirements({{"targetId", true}}); + factory->addRequirements({{"deviceUuid", true}}); } return factory; diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 3d9cda681..547132d35 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -223,6 +223,7 @@ add_agent_test(pallet FALSE asset) add_agent_test(fixture FALSE asset) add_agent_test(target FALSE asset) add_agent_test(process FALSE asset) +add_agent_test(part FALSE asset) add_agent_test(agent_device TRUE device_model) add_agent_test(component FALSE device_model) diff --git a/test_package/part_test.cpp b/test_package/part_test.cpp new file mode 100644 index 000000000..f28fab99f --- /dev/null +++ b/test_package/part_test.cpp @@ -0,0 +1,657 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include +#include +#include +#include +#include + +#include "agent_test_helper.hpp" +#include "json_helper.hpp" +#include "mtconnect/agent.hpp" +#include "mtconnect/asset/asset.hpp" +#include "mtconnect/asset/part.hpp" +#include "mtconnect/entity/xml_parser.hpp" +#include "mtconnect/entity/xml_printer.hpp" +#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/source/adapter/adapter.hpp" + +using json = nlohmann::json; +using namespace std; +using namespace mtconnect; +using namespace mtconnect::entity; +using namespace mtconnect::source::adapter; +using namespace mtconnect::asset; +using namespace mtconnect::printer; + +// main +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class PartAssetTest : public testing::Test +{ +protected: + void SetUp() override + { // Create an agent with only 16 slots and 8 data items. + Part::registerAsset(); + PartArchetype::registerAsset(); + m_writer = make_unique(true); + } + + void TearDown() override { m_writer.reset(); } + + std::unique_ptr m_writer; + std::unique_ptr m_agentTestHelper; +}; + +TEST_F(PartAssetTest, should_parse_a_part_archetype) +{ + const auto doc = + R"DOC( + + + + + + + + +
100 Fruitstand Rd, Ork Arkansas, 11111
+ Some customer +
+
+
+)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + ASSERT_EQ("PartArchetype", asset->getName()); + ASSERT_EQ("PART1234", asset->getAssetId()); + ASSERT_EQ("5", asset->get("revision")); + ASSERT_EQ("STEP222", asset->get("drawing")); + ASSERT_EQ("HHH", asset->get("family")); + + auto configuration = asset->get("Configuration"); + ASSERT_TRUE(configuration); + + auto relationships = configuration->getList("Relationships"); + ASSERT_TRUE(relationships); + ASSERT_EQ(2, relationships->size()); + + { + auto it = relationships->begin(); + ASSERT_EQ("A", (*it)->get("id")); + ASSERT_EQ("MATERIAL", (*it)->get("assetIdRef")); + ASSERT_EQ("PEER", (*it)->get("type")); + ASSERT_EQ("RawMaterial", (*it)->get("assetType")); + + it++; + ASSERT_EQ("B", (*it)->get("id")); + ASSERT_EQ("PROCESS", (*it)->get("assetIdRef")); + ASSERT_EQ("PEER", (*it)->get("type")); + ASSERT_EQ("ProcessArchetype", (*it)->get("assetType")); + } + + auto customers = asset->getList("Customers"); + ASSERT_TRUE(customers); + ASSERT_EQ(1, customers->size()); + + { + auto customer = customers->front(); + ASSERT_EQ("C00241", customer->get("customerId")); + ASSERT_EQ("customer name", customer->get("name")); + ASSERT_EQ("100 Fruitstand Rd, Ork Arkansas, 11111", customer->get("Address")); + ASSERT_EQ("Some customer", customer->get("Description")); + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + + +TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) +{ + const auto doc = + R"DOC( + + +
100 Fruitstand Rd, Ork Arkansas, 11111
+ Some customer +
+ +
Somewhere in Austrailia
+ Another customer +
+
+
+)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + ASSERT_EQ("PartArchetype", asset->getName()); + + auto customers = asset->getList("Customers"); + ASSERT_TRUE(customers); + ASSERT_EQ(2, customers->size()); + + { + auto it = customers->begin(); + auto customer = *it; + EXPECT_EQ("C00241", customer->get("customerId")); + EXPECT_EQ("customer name", customer->get("name")); + EXPECT_EQ("100 Fruitstand Rd, Ork Arkansas, 11111", customer->get("Address")); + EXPECT_EQ("Some customer", customer->get("Description")); + + it++; + customer = *it; + EXPECT_EQ("C1111", customer->get("customerId")); + EXPECT_EQ("another customer", customer->get("name")); + EXPECT_EQ("Somewhere in Austrailia", customer->get("Address")); + EXPECT_EQ("Another customer", customer->get("Description")); + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +/* +TEST_F(PartAssetTest, process_steps_can_be_optional) +{ + const auto doc = + R"DOC( + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(1, routings->size()); + + auto routing = routings->front(); + ASSERT_EQ("routng1", routing->get("routingId")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + auto step = processSteps.front(); + + ASSERT_EQ("10", step->get("stepId")); + ASSERT_EQ(5, step->get("sequence")); + ASSERT_EQ(true, step->get("optional")); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(PartAssetTest, process_archetype_must_have_at_least_one_routing) +{ + const auto doc = + R"DOC( + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + auto error = dynamic_cast(errors.front().get()); + + ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + ASSERT_EQ("ProcessArchetype", error->getEntity()); + ASSERT_EQ("Routings", error->getProperty()); +} + +TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) +{ + const auto doc = + R"DOC( + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(5, errors.size()); + + auto it = errors.begin(); + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, error->what()); + EXPECT_EQ("Routing", error->getEntity()); + EXPECT_EQ("ProcessStep", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("Routings: Invalid element 'Routing'"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 found"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + EXPECT_EQ("Routing", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("ProcessArchetype: Invalid element 'Routings'"s, error->what()); + EXPECT_EQ("ProcessArchetype", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + EXPECT_EQ("ProcessArchetype", error->getEntity()); + EXPECT_EQ("Routings", error->getProperty()); + } + +} + +TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) +{ + const auto doc = + R"DOC( + + + + + + + First Activity + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto routings = asset->getList("Routings"); + ASSERT_TRUE(routings); + ASSERT_EQ(1, routings->size()); + + auto routing = routings->front(); + ASSERT_EQ("routng1", routing->get("routingId")); + + auto processSteps = routing->get("ProcessStep"); + ASSERT_EQ(1, processSteps.size()); + auto step = processSteps.front(); + + auto activityGroups = step->getList("ActivityGroups"); + ASSERT_TRUE(activityGroups); + ASSERT_EQ(1, activityGroups->size()); + + auto activityGroup = activityGroups->front(); + ASSERT_EQ("act1", activityGroup->get("activityGroupId")); + ASSERT_EQ("fred", activityGroup->get("name")); + + auto activities = activityGroup->get("Activity"); + ASSERT_EQ(1, activities.size()); + + auto activity = activities.front(); + ASSERT_EQ("a1", activity->get("activityId")); + ASSERT_EQ(2, activity->get("sequence")); + ASSERT_EQ("First Activity", activity->get("Description")); + ASSERT_EQ(true, activity->get("optional")); + ASSERT_EQ(3, activity->get("precedence")); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(PartAssetTest, should_generate_json) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + + + First Activity + + + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + entity::JsonEntityPrinter jprinter(2, true); + + auto sdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "ProcessArchetype": { + "Configuration": { + "Relationships": { + "AssetRelationship": [ + { + "assetIdRef": "PART_ID", + "assetType": "PART_ARCHETYPE", + "id": "reference_id", + "type": "PEER" + } + ] + } + }, + "Routings": { + "Routing": [ + { + "ProcessStep": [ + { + "ActivityGroups": { + "ActivityGroup": [ + { + "Activity": [ + { + "Description": "First Activity", + "activityId": "a1", + "optional": true, + "precedence": 2, + "sequence": 1 + } + ], + "activityGroupId": "act1", + "name": "fred" + } + ] + }, + "Description": "Process Step 10", + "Duration": 23000.0, + "StartTime": "2025-11-24T00:00:00Z", + "Targets": { + "TargetRef": [ + { + "groupIdRef": "group1" + } + ] + }, + "stepId": "10" + } + ], + "precedence": 1, + "routingId": "routng1" + } + ] + }, + "Targets": { + "TargetDevice": [ + { + "targetId": "device1" + } + ], + "TargetGroup": [ + { + "TargetDevice": [ + { + "targetId": "device2" + }, + { + "targetId": "device3" + } + ], + "groupId": "group1" + } + ] + }, + "assetId": "PROCESS_ARCH_ID", + "revision": "1" + } +})", sdoc); +} + +TEST_F(PartAssetTest, should_parse_and_generate_a_process) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + + + First Activity + + + + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(PartAssetTest, process_can_only_have_one_routings) +{ + const auto doc = + R"DOC( + + + + + + + + + Process Step 10 + 2025-11-24T00:00:00Z + 23000 + + + + + Process Step 11 + 2025-11-25T00:00:00Z + 20000 + + + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + auto it = errors.begin(); + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more than 1 entries, 2 found"s, error->what()); + EXPECT_EQ("Routings", error->getEntity()); + EXPECT_EQ("Routing", error->getProperty()); + } + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("Process: Invalid element 'Routings'"s, error->what()); + EXPECT_EQ("Process", error->getEntity()); + } + + it++; + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ("Process(Routings): Property Routings is required and not provided"s, error->what()); + EXPECT_EQ("Process", error->getEntity()); + EXPECT_EQ("Routings", error->getProperty()); + } +} +*/ diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp index c9a53c56e..f7d85904e 100644 --- a/test_package/process_test.cpp +++ b/test_package/process_test.cpp @@ -96,10 +96,10 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) - + - - + + @@ -188,7 +188,7 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) { auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); - ASSERT_EQ("device1", (*it)->get("targetId")); + ASSERT_EQ("device1", (*it)->get("deviceUuid")); it++; auto targetGroup = *it; @@ -201,10 +201,10 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) { auto dit = targetDevices.begin(); ASSERT_EQ("TargetDevice", (*dit)->getName()); - ASSERT_EQ("device2", (*dit)->get("targetId")); + ASSERT_EQ("device2", (*dit)->get("deviceUuid")); dit++; ASSERT_EQ("TargetDevice", (*dit)->getName()); - ASSERT_EQ("device3", (*dit)->get("targetId")); + ASSERT_EQ("device3", (*dit)->get("deviceUuid")); } } @@ -243,10 +243,10 @@ TEST_F(ProcessAssetTest, process_archetype_can_have_multiple_routings) - + - - + + @@ -361,10 +361,10 @@ TEST_F(ProcessAssetTest, process_archetype_must_have_at_least_one_routing) const auto doc = R"DOC( - + - - + + @@ -391,10 +391,10 @@ TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) - + - - + + @@ -546,10 +546,10 @@ TEST_F(ProcessAssetTest, should_generate_json) - + - - + + @@ -621,17 +621,17 @@ TEST_F(ProcessAssetTest, should_generate_json) "Targets": { "TargetDevice": [ { - "targetId": "device1" + "deviceUuid": "device1" } ], "TargetGroup": [ { "TargetDevice": [ { - "targetId": "device2" + "deviceUuid": "device2" }, { - "targetId": "device3" + "deviceUuid": "device3" } ], "groupId": "group1" @@ -673,10 +673,10 @@ TEST_F(ProcessAssetTest, should_parse_and_generate_a_process) - + - - + + @@ -722,10 +722,10 @@ TEST_F(ProcessAssetTest, process_can_only_have_one_routings) - + - - + + diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index 694afbbc1..06b03ef15 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -72,7 +72,7 @@ TEST_F(TargetTest, simple_target_device) const auto doc = R"DOC( - + )DOC"; @@ -96,7 +96,7 @@ TEST_F(TargetTest, simple_target_device) auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); - ASSERT_EQ("device-1234", (*it)->get("targetId")); + ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); } TEST_F(TargetTest, target_device_and_device_group) @@ -104,10 +104,10 @@ TEST_F(TargetTest, target_device_and_device_group) const auto doc = R"DOC( - + - - + + @@ -132,7 +132,7 @@ TEST_F(TargetTest, target_device_and_device_group) auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); - ASSERT_EQ("device-1234", (*it)->get("targetId")); + ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); it++; auto group = *it; @@ -146,11 +146,11 @@ TEST_F(TargetTest, target_device_and_device_group) auto git = groupTargets.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); - ASSERT_EQ("device-5678", (*git)->get("targetId")); + ASSERT_EQ("device-5678", (*git)->get("deviceUuid")); git++; ASSERT_EQ("TargetDevice", (*git)->getName()); - ASSERT_EQ("device-9999", (*git)->get("targetId")); + ASSERT_EQ("device-9999", (*git)->get("deviceUuid")); } TEST_F(TargetTest, target_device_and_device_group_json) @@ -158,10 +158,10 @@ TEST_F(TargetTest, target_device_and_device_group_json) const auto doc = R"DOC( - + - - + + @@ -188,17 +188,17 @@ TEST_F(TargetTest, target_device_and_device_group_json) "Targets": { "TargetDevice": [ { - "targetId": "device-1234" + "deviceUuid": "device-1234" } ], "TargetGroup": [ { "TargetDevice": [ { - "targetId": "device-5678" + "deviceUuid": "device-5678" }, { - "targetId": "device-9999" + "deviceUuid": "device-9999" } ], "groupId": "group_id" @@ -214,13 +214,13 @@ TEST_F(TargetTest, nested_target_groups_with_target_refs) const auto doc = R"DOC( - + - - + + - + @@ -246,7 +246,7 @@ TEST_F(TargetTest, nested_target_groups_with_target_refs) auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); - ASSERT_EQ("device-1234", (*it)->get("targetId")); + ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); it++; auto group = *it; @@ -260,11 +260,11 @@ TEST_F(TargetTest, nested_target_groups_with_target_refs) auto git = groupTargets.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); - ASSERT_EQ("device-5678", (*git)->get("targetId")); + ASSERT_EQ("device-5678", (*git)->get("deviceUuid")); git++; ASSERT_EQ("TargetDevice", (*git)->getName()); - ASSERT_EQ("device-9999", (*git)->get("targetId")); + ASSERT_EQ("device-9999", (*git)->get("deviceUuid")); group = *(++it); ASSERT_EQ("TargetGroup", group->getName()); @@ -276,7 +276,7 @@ TEST_F(TargetTest, nested_target_groups_with_target_refs) git = groupTargets2.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); - ASSERT_EQ("device-2222", (*git)->get("targetId")); + ASSERT_EQ("device-2222", (*git)->get("deviceUuid")); git++; ASSERT_EQ("TargetRef", (*git)->getName()); @@ -289,7 +289,7 @@ TEST_F(TargetTest, reject_empty_groups) const auto doc = R"DOC( - + @@ -315,7 +315,7 @@ TEST_F(TargetTest, reject_empty_groups) auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); - ASSERT_EQ("device-1234", (*it)->get("targetId")); + ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); } TEST_F(TargetTest, verify_target_requirement) From 260aaf176aaaad2006362acb7b90a347f7f11091 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 26 Nov 2025 18:07:27 +0100 Subject: [PATCH 55/84] Formatted --- src/mtconnect/agent.cpp | 2 +- src/mtconnect/asset/asset.cpp | 13 +- src/mtconnect/asset/part.cpp | 52 ++++---- src/mtconnect/asset/part.hpp | 1 - src/mtconnect/asset/process.cpp | 76 ++++++----- src/mtconnect/asset/process.hpp | 1 - src/mtconnect/asset/target.cpp | 85 ++++++------ src/mtconnect/asset/target.hpp | 3 +- src/mtconnect/utilities.hpp | 5 +- test_package/json_printer_test.cpp | 6 +- test_package/mqtt_entity_sink_test.cpp | 19 ++- test_package/part_test.cpp | 136 +++++++++---------- test_package/process_test.cpp | 172 +++++++++++++------------ test_package/target_test.cpp | 144 ++++++++++----------- 14 files changed, 352 insertions(+), 363 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 5371cf58a..b9162f65d 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -48,8 +48,8 @@ #include "mtconnect/asset/file_asset.hpp" #include "mtconnect/asset/fixture.hpp" #include "mtconnect/asset/pallet.hpp" -#include "mtconnect/asset/process.hpp" #include "mtconnect/asset/part.hpp" +#include "mtconnect/asset/process.hpp" #include "mtconnect/asset/qif_document.hpp" #include "mtconnect/asset/raw_material.hpp" #include "mtconnect/configuration/config_options.hpp" diff --git a/src/mtconnect/asset/asset.cpp b/src/mtconnect/asset/asset.cpp index cb33e24c8..c30926825 100644 --- a/src/mtconnect/asset/asset.cpp +++ b/src/mtconnect/asset/asset.cpp @@ -16,11 +16,12 @@ // #include "asset.hpp" -#include "mtconnect/device_model/configuration/configuration.hpp" #include #include +#include "mtconnect/device_model/configuration/configuration.hpp" + using namespace std; namespace mtconnect { @@ -30,11 +31,11 @@ namespace mtconnect { { using namespace device_model::configuration; static auto asset = make_shared( - Requirements({Requirement("assetId", false), Requirement("deviceUuid", false), - Requirement("timestamp", ValueType::TIMESTAMP, false), - Requirement("hash", false), - Requirement("Configuration", ValueType::ENTITY, Configuration::getFactory(), false), - Requirement("removed", ValueType::BOOL, false)}), + Requirements( + {Requirement("assetId", false), Requirement("deviceUuid", false), + Requirement("timestamp", ValueType::TIMESTAMP, false), Requirement("hash", false), + Requirement("Configuration", ValueType::ENTITY, Configuration::getFactory(), false), + Requirement("removed", ValueType::BOOL, false)}), [](const std::string &name, Properties &props) -> EntityPtr { return make_shared(name, props); }); diff --git a/src/mtconnect/asset/part.cpp b/src/mtconnect/asset/part.cpp index abe65c222..7257dd305 100644 --- a/src/mtconnect/asset/part.cpp +++ b/src/mtconnect/asset/part.cpp @@ -28,27 +28,26 @@ namespace mtconnect { if (!factory) { auto customer = make_shared(Requirements { - {"customerId", true}, - {"name", false}, - {"Address", false}, - {"Description", false}, + {"customerId", true}, + {"name", false}, + {"Address", false}, + {"Description", false}, }); - + auto customers = make_shared(Requirements { - {"Customer", ValueType::ENTITY, customer, 1, entity::Requirement::Infinite} - }); - + {"Customer", ValueType::ENTITY, customer, 1, entity::Requirement::Infinite}}); + factory = make_shared(*Asset::getFactory()); factory->addRequirements({ - {"revision", true}, - {"family", false}, - {"drawing", false}, - {"Customers", ValueType::ENTITY_LIST, customers, true}, + {"revision", true}, + {"family", false}, + {"drawing", false}, + {"Customers", ValueType::ENTITY_LIST, customers, true}, }); } return factory; } - + void PartArchetype::registerAsset() { static bool once {true}; @@ -65,31 +64,24 @@ namespace mtconnect { if (!factory) { auto identifier = make_shared(Requirements { - {"type", true}, - {"stepIdRef", false}, - {"timestamp", ValueType::TIMESTAMP, true} - }); - - + {"type", true}, {"stepIdRef", false}, {"timestamp", ValueType::TIMESTAMP, true}}); + auto identifiers = make_shared(Requirements { - {"UniqueIdentitier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}, - {"GroupIdentifier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite} - }); + {"UniqueIdentitier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}, + {"GroupIdentifier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}}); identifiers->setMinListSize(1); identifiers->registerMatchers(); - + factory = make_shared(*Asset::getFactory()); - factory->addRequirements({ - {"revision", true}, - {"family", false}, - {"drawing", false}, - {"PartIdentifiers", ValueType::ENTITY_LIST, identifiers, false} - }); + factory->addRequirements({{"revision", true}, + {"family", false}, + {"drawing", false}, + {"PartIdentifiers", ValueType::ENTITY_LIST, identifiers, false}}); } return factory; } - + void Part::registerAsset() { static bool once {true}; diff --git a/src/mtconnect/asset/part.hpp b/src/mtconnect/asset/part.hpp index 2f5acbaba..6036ce19a 100644 --- a/src/mtconnect/asset/part.hpp +++ b/src/mtconnect/asset/part.hpp @@ -24,7 +24,6 @@ #include "asset.hpp" #include "mtconnect/entity/factory.hpp" #include "mtconnect/utilities.hpp" -#include "asset.hpp" namespace mtconnect::asset { /// @brief Manufacturing process archetype asset diff --git a/src/mtconnect/asset/process.cpp b/src/mtconnect/asset/process.cpp index 4b3aebc8e..4154cf4c4 100644 --- a/src/mtconnect/asset/process.cpp +++ b/src/mtconnect/asset/process.cpp @@ -16,6 +16,7 @@ // #include "process.hpp" + #include "target.hpp" using namespace std; @@ -28,57 +29,54 @@ namespace mtconnect { static FactoryPtr factory; if (!factory) { - auto activity = make_shared(Requirements { - {"sequence", ValueType::INTEGER, false}, - {"activityId", true}, - {"precedence", ValueType::INTEGER, false}, - {"optional", ValueType::BOOL, false}, - {"Description", false} - }); - + auto activity = + make_shared(Requirements {{"sequence", ValueType::INTEGER, false}, + {"activityId", true}, + {"precedence", ValueType::INTEGER, false}, + {"optional", ValueType::BOOL, false}, + {"Description", false}}); + auto activityGroup = make_shared(Requirements { - {"activityGroupId", true}, - {"name", false}, - {"Activity", ValueType::ENTITY, activity, 1, entity::Requirement::Infinite}, + {"activityGroupId", true}, + {"name", false}, + {"Activity", ValueType::ENTITY, activity, 1, entity::Requirement::Infinite}, }); - + auto activityGroups = make_shared(Requirements { - {"ActivityGroup", ValueType::ENTITY, activityGroup, 1, entity::Requirement::Infinite} - }); - - auto processStep = make_shared(Requirements {{ - {"stepId", true}, - {"optional", ValueType::BOOL, false}, - {"sequence", ValueType::INTEGER, false}, - {"Description", false}, - {"StartTime", ValueType::TIMESTAMP, false}, - {"Duration", ValueType::DOUBLE, false}, - {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, - {"ActivityGroups", ValueType::ENTITY_LIST, activityGroups, false} - }}); - processStep->setOrder({"Description", "StartTime", "Duration", "Targets", "ActivityGroups"}); - + {"ActivityGroup", ValueType::ENTITY, activityGroup, 1, entity::Requirement::Infinite}}); + + auto processStep = make_shared( + Requirements {{{"stepId", true}, + {"optional", ValueType::BOOL, false}, + {"sequence", ValueType::INTEGER, false}, + {"Description", false}, + {"StartTime", ValueType::TIMESTAMP, false}, + {"Duration", ValueType::DOUBLE, false}, + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, + {"ActivityGroups", ValueType::ENTITY_LIST, activityGroups, false}}}); + processStep->setOrder( + {"Description", "StartTime", "Duration", "Targets", "ActivityGroups"}); + auto routing = make_shared(Requirements { - {"precedence", ValueType::INTEGER, true}, - {"routingId", true}, - {"ProcessStep", ValueType::ENTITY, processStep, 1, entity::Requirement::Infinite}, + {"precedence", ValueType::INTEGER, true}, + {"routingId", true}, + {"ProcessStep", ValueType::ENTITY, processStep, 1, entity::Requirement::Infinite}, }); - + auto routings = make_shared(Requirements { - {"Routing", ValueType::ENTITY, routing, 1, entity::Requirement::Infinite} - }); - + {"Routing", ValueType::ENTITY, routing, 1, entity::Requirement::Infinite}}); + factory = make_shared(*Asset::getFactory()); factory->addRequirements({ - {"revision", true}, - {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, - {"Routings", ValueType::ENTITY_LIST, routings, true}, + {"revision", true}, + {"Targets", ValueType::ENTITY_LIST, Target::getTargetsFactory(), false}, + {"Routings", ValueType::ENTITY_LIST, routings, true}, }); factory->setOrder({"Configuration", "Routings", "Targets"}); } return factory; } - + void ProcessArchetype::registerAsset() { static bool once {true}; @@ -101,7 +99,7 @@ namespace mtconnect { } return factory; } - + void Process::registerAsset() { static bool once {true}; diff --git a/src/mtconnect/asset/process.hpp b/src/mtconnect/asset/process.hpp index 3944036a7..ccc3a43e9 100644 --- a/src/mtconnect/asset/process.hpp +++ b/src/mtconnect/asset/process.hpp @@ -24,7 +24,6 @@ #include "asset.hpp" #include "mtconnect/entity/factory.hpp" #include "mtconnect/utilities.hpp" -#include "asset.hpp" namespace mtconnect::asset { /// @brief Manufacturing process archetype asset diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp index f165bc88b..77880eccd 100644 --- a/src/mtconnect/asset/target.cpp +++ b/src/mtconnect/asset/target.cpp @@ -27,21 +27,22 @@ namespace mtconnect { static FactoryPtr target = make_shared(); return target; } - + FactoryPtr Target::getDeviceTargetsFactory() { static FactoryPtr targets; if (!targets) { targets = make_shared(entity::Requirements { - entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite) - }); - + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, + entity::Requirement::Infinite)}); + targets->registerMatchers(); targets->setMinListSize(1); } - + return targets; } @@ -51,55 +52,59 @@ namespace mtconnect { if (!targets) { targets = make_shared(entity::Requirements { - entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite) - }); - + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, + entity::Requirement::Infinite)}); + targets->registerMatchers(); targets->setMinListSize(1); } - + return targets; } - + FactoryPtr Target::getAllTargetsFactory() { static FactoryPtr targets; if (!targets) { targets = make_shared(entity::Requirements { - entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite) - }); - + entity::Requirement("TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetGroup", ValueType::ENTITY_LIST, TargetGroup::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, + entity::Requirement::Infinite), + entity::Requirement("TargetRequirement", ValueType::ENTITY, + TargetRequirement::getFactory(), 0, + entity::Requirement::Infinite)}); + targets->registerMatchers(); targets->setMinListSize(1); } - + return targets; } - FactoryPtr Target::getRequirementTargetsFactory() { static FactoryPtr targets; if (!targets) { - targets = make_shared(entity::Requirements { - entity::Requirement("TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite) - }); - + targets = make_shared(entity::Requirements {entity::Requirement( + "TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, + entity::Requirement::Infinite)}); + targets->registerMatchers(); targets->setMinListSize(1); } - + return targets; } - FactoryPtr TargetDevice::getFactory() { static FactoryPtr factory; @@ -108,10 +113,10 @@ namespace mtconnect { factory = make_shared(*Target::getFactory()); factory->addRequirements({{"deviceUuid", true}}); } - + return factory; } - + FactoryPtr TargetRef::getFactory() { static FactoryPtr factory; @@ -120,29 +125,29 @@ namespace mtconnect { factory = make_shared(*Target::getFactory()); factory->addRequirements({{"groupIdRef", true}}); } - + return factory; } - + FactoryPtr TargetGroup::getFactory() { using namespace entity; static FactoryPtr factory; if (!factory) { - factory = make_shared(*Target::getFactory()); factory->addRequirements({{"groupId", true}, - {"TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, entity::Requirement::Infinite}, - {"TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite} - }); + {"TargetDevice", ValueType::ENTITY, TargetDevice::getFactory(), 0, + entity::Requirement::Infinite}, + {"TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, + entity::Requirement::Infinite}}); factory->registerMatchers(); factory->setMinListSize(1); } return factory; } - + FactoryPtr TargetRequirement::getFactory() { using namespace entity; @@ -150,11 +155,11 @@ namespace mtconnect { if (!factory) { factory = make_shared(*Target::getFactory()); - factory->addRequirements({{"requirementId", true}, - {"CapabilityTable", ValueType::TABLE, false}}); + factory->addRequirements( + {{"requirementId", true}, {"CapabilityTable", ValueType::TABLE, false}}); } - + return factory; } - } + } // namespace asset } // namespace mtconnect diff --git a/src/mtconnect/asset/target.hpp b/src/mtconnect/asset/target.hpp index cb6b8ed1d..43e52fb92 100644 --- a/src/mtconnect/asset/target.hpp +++ b/src/mtconnect/asset/target.hpp @@ -21,10 +21,10 @@ #include #include +#include "asset.hpp" #include "mtconnect/entity/entity.hpp" #include "mtconnect/entity/factory.hpp" #include "mtconnect/utilities.hpp" -#include "asset.hpp" namespace mtconnect::asset { /// @brief A target of a process or a task @@ -66,5 +66,4 @@ namespace mtconnect::asset { static entity::FactoryPtr getFactory(); }; - } // namespace mtconnect::asset diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index dff98f5d8..0b10cc783 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -241,14 +241,15 @@ namespace mtconnect { /// @brief Thread safe localtime function that uses localtime_s or localtime_r based on platform /// @param[in] timer pointer to time_t /// @param[out] buf pointer to tm struct to fill - inline void safe_localtime(const std::time_t* timer, std::tm* buf) { + inline void safe_localtime(const std::time_t *timer, std::tm *buf) + { #ifdef _WINDOWS localtime_s(buf, timer); #else localtime_r(timer, buf); #endif } - + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format diff --git a/test_package/json_printer_test.cpp b/test_package/json_printer_test.cpp index 9bbcf0789..dd35fc59c 100644 --- a/test_package/json_printer_test.cpp +++ b/test_package/json_printer_test.cpp @@ -356,10 +356,8 @@ TEST_F(JsonPrinterTest, elements_with_property_list_version_2) json jdoc = json::parse(sdoc); ASSERT_EQ(2, jdoc.at("/Root/CuttingItems/count"_json_pointer).get()); - ASSERT_EQ("1", - jdoc.at("/Root/CuttingItems/CuttingItem/0/itemId"_json_pointer).get()); - ASSERT_EQ("2", - jdoc.at("/Root/CuttingItems/CuttingItem/1/itemId"_json_pointer).get()); + ASSERT_EQ("1", jdoc.at("/Root/CuttingItems/CuttingItem/0/itemId"_json_pointer).get()); + ASSERT_EQ("2", jdoc.at("/Root/CuttingItems/CuttingItem/1/itemId"_json_pointer).get()); } TEST_F(JsonPrinterTest, should_honor_include_hidden_parameter) diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index f5b781ab1..4cc274038 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -218,16 +218,15 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) client->set_keep_alive_sec(30); bool subscribed = false; - client->set_connack_handler( - [client](bool sp, mqtt::connect_return_code connack_return_code) { - if (connack_return_code == mqtt::connect_return_code::accepted) - { - auto pid = client->acquire_unique_packet_id(); - client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, - [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); - } - return true; - }); + client->set_connack_handler([client](bool sp, mqtt::connect_return_code connack_return_code) { + if (connack_return_code == mqtt::connect_return_code::accepted) + { + auto pid = client->acquire_unique_packet_id(); + client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, + [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); + } + return true; + }); client->set_suback_handler( [&subscribed](std::uint16_t packet_id, std::vector results) { diff --git a/test_package/part_test.cpp b/test_package/part_test.cpp index f28fab99f..3611d6a4d 100644 --- a/test_package/part_test.cpp +++ b/test_package/part_test.cpp @@ -103,29 +103,29 @@ TEST_F(PartAssetTest, should_parse_a_part_archetype) auto configuration = asset->get("Configuration"); ASSERT_TRUE(configuration); - + auto relationships = configuration->getList("Relationships"); ASSERT_TRUE(relationships); ASSERT_EQ(2, relationships->size()); - + { auto it = relationships->begin(); ASSERT_EQ("A", (*it)->get("id")); ASSERT_EQ("MATERIAL", (*it)->get("assetIdRef")); ASSERT_EQ("PEER", (*it)->get("type")); ASSERT_EQ("RawMaterial", (*it)->get("assetType")); - + it++; ASSERT_EQ("B", (*it)->get("id")); ASSERT_EQ("PROCESS", (*it)->get("assetIdRef")); ASSERT_EQ("PEER", (*it)->get("type")); ASSERT_EQ("ProcessArchetype", (*it)->get("assetType")); } - + auto customers = asset->getList("Customers"); ASSERT_TRUE(customers); ASSERT_EQ(1, customers->size()); - + { auto customer = customers->front(); ASSERT_EQ("C00241", customer->get("customerId")); @@ -133,7 +133,7 @@ TEST_F(PartAssetTest, should_parse_a_part_archetype) ASSERT_EQ("100 Fruitstand Rd, Ork Arkansas, 11111", customer->get("Address")); ASSERT_EQ("Some customer", customer->get("Description")); } - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); @@ -142,7 +142,6 @@ TEST_F(PartAssetTest, should_parse_a_part_archetype) ASSERT_EQ(content, doc); } - TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) { const auto doc = @@ -162,10 +161,10 @@ TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); ASSERT_EQ("PartArchetype", asset->getName()); @@ -173,7 +172,7 @@ TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) auto customers = asset->getList("Customers"); ASSERT_TRUE(customers); ASSERT_EQ(2, customers->size()); - + { auto it = customers->begin(); auto customer = *it; @@ -181,7 +180,7 @@ TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) EXPECT_EQ("customer name", customer->get("name")); EXPECT_EQ("100 Fruitstand Rd, Ork Arkansas, 11111", customer->get("Address")); EXPECT_EQ("Some customer", customer->get("Description")); - + it++; customer = *it; EXPECT_EQ("C1111", customer->get("customerId")); @@ -193,7 +192,7 @@ TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -214,27 +213,27 @@ TEST_F(PartAssetTest, process_steps_can_be_optional) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(1, routings->size()); - + auto routing = routings->front(); ASSERT_EQ("routng1", routing->get("routingId")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); auto step = processSteps.front(); - + ASSERT_EQ("10", step->get("stepId")); ASSERT_EQ(5, step->get("sequence")); ASSERT_EQ(true, step->get("optional")); @@ -242,7 +241,7 @@ TEST_F(PartAssetTest, process_steps_can_be_optional) // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -260,17 +259,17 @@ TEST_F(PartAssetTest, process_archetype_must_have_at_least_one_routing) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(1, errors.size()); auto error = dynamic_cast(errors.front().get()); - - ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); - ASSERT_EQ("ProcessArchetype", error->getEntity()); - ASSERT_EQ("Routings", error->getProperty()); + + ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, +error->what()); ASSERT_EQ("ProcessArchetype", error->getEntity()); ASSERT_EQ("Routings", +error->getProperty()); } TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) @@ -290,22 +289,22 @@ TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(5, errors.size()); - + auto it = errors.begin(); { auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, error->what()); - EXPECT_EQ("Routing", error->getEntity()); - EXPECT_EQ("ProcessStep", error->getProperty()); + EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, +error->what()); EXPECT_EQ("Routing", error->getEntity()); EXPECT_EQ("ProcessStep", +error->getProperty()); } - + it++; { auto error = it->get(); @@ -313,14 +312,14 @@ TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) EXPECT_EQ("Routings: Invalid element 'Routing'"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); } - + it++; { auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 found"s, error->what()); - EXPECT_EQ("Routings", error->getEntity()); - EXPECT_EQ("Routing", error->getProperty()); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 +found"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); EXPECT_EQ("Routing", +error->getProperty()); } it++; @@ -335,11 +334,11 @@ TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) { auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); - EXPECT_EQ("ProcessArchetype", error->getEntity()); - EXPECT_EQ("Routings", error->getProperty()); + EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, +error->what()); EXPECT_EQ("ProcessArchetype", error->getEntity()); EXPECT_EQ("Routings", +error->getProperty()); } - + } TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) @@ -364,20 +363,20 @@ TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(1, routings->size()); - + auto routing = routings->front(); ASSERT_EQ("routng1", routing->get("routingId")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); auto step = processSteps.front(); @@ -385,14 +384,14 @@ TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) auto activityGroups = step->getList("ActivityGroups"); ASSERT_TRUE(activityGroups); ASSERT_EQ(1, activityGroups->size()); - + auto activityGroup = activityGroups->front(); ASSERT_EQ("act1", activityGroup->get("activityGroupId")); ASSERT_EQ("fred", activityGroup->get("name")); - + auto activities = activityGroup->get("Activity"); ASSERT_EQ(1, activities.size()); - + auto activity = activities.front(); ASSERT_EQ("a1", activity->get("activityId")); ASSERT_EQ(2, activity->get("sequence")); @@ -403,7 +402,7 @@ TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -414,7 +413,8 @@ TEST_F(PartAssetTest, should_generate_json) R"DOC( - + @@ -445,15 +445,15 @@ TEST_F(PartAssetTest, should_generate_json) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + entity::JsonEntityPrinter jprinter(2, true); - + auto sdoc = jprinter.print(entity); EXPECT_EQ(R"({ "ProcessArchetype": { @@ -541,7 +541,8 @@ TEST_F(PartAssetTest, should_parse_and_generate_a_process) R"DOC( - + @@ -572,17 +573,17 @@ TEST_F(PartAssetTest, should_parse_and_generate_a_process) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -593,7 +594,8 @@ TEST_F(PartAssetTest, process_can_only_have_one_routings) R"DOC( - + @@ -621,22 +623,22 @@ TEST_F(PartAssetTest, process_can_only_have_one_routings) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(3, errors.size()); - + auto it = errors.begin(); { auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more than 1 entries, 2 found"s, error->what()); - EXPECT_EQ("Routings", error->getEntity()); + EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more +than 1 entries, 2 found"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); EXPECT_EQ("Routing", error->getProperty()); } - + it++; { auto error = it->get(); diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp index f7d85904e..ca659ad88 100644 --- a/test_package/process_test.cpp +++ b/test_package/process_test.cpp @@ -120,11 +120,11 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) auto configuration = asset->get("Configuration"); ASSERT_TRUE(configuration); - + auto relationships = configuration->getList("Relationships"); ASSERT_TRUE(relationships); ASSERT_EQ(1, relationships->size()); - + { auto it = relationships->begin(); ASSERT_EQ("reference_id", (*it)->get("id")); @@ -132,72 +132,72 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) ASSERT_EQ("PEER", (*it)->get("type")); ASSERT_EQ("PART_ARCHETYPE", (*it)->get("assetType")); } - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(1, routings->size()); - + { auto routing = routings->front(); ASSERT_EQ("routng1", routing->get("routingId")); ASSERT_EQ(1, routing->get("precedence")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); - + auto step = processSteps.front(); ASSERT_EQ("10", step->get("stepId")); ASSERT_EQ("Process Step 10", step->get("Description")); - + auto st = step->get("StartTime"); ASSERT_EQ("2025-11-24T00:00:00Z", getCurrentTime(st, GMT)); ASSERT_EQ(23000, step->get("Duration")); - + auto targets = step->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(1, targets->size()); - + { auto it = targets->begin(); ASSERT_EQ("group1", (*it)->get("groupIdRef")); } - + auto activityGroups = step->getList("ActivityGroups"); ASSERT_TRUE(activityGroups); ASSERT_EQ(1, activityGroups->size()); - + { auto it = activityGroups->begin(); auto activityGroup = *it; ASSERT_EQ("act1", activityGroup->get("activityGroupId")); - + auto activities = activityGroup->get("Activity"); ASSERT_EQ(1, activities.size()); - + auto activity = activities.front(); ASSERT_EQ("a1", activity->get("activityId")); ASSERT_EQ(1, activity->get("sequence")); ASSERT_EQ("First Activity", activity->get("Description")); } } - + auto targets = asset->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(2, targets->size()); - + { auto it = targets->begin(); ASSERT_EQ("TargetDevice", (*it)->getName()); ASSERT_EQ("device1", (*it)->get("deviceUuid")); - + it++; auto targetGroup = *it; ASSERT_EQ("TargetGroup", targetGroup->getName()); ASSERT_EQ("group1", targetGroup->get("groupId")); - + auto targetDevices = targetGroup->get("LIST"); ASSERT_EQ(2, targetDevices.size()); - + { auto dit = targetDevices.begin(); ASSERT_EQ("TargetDevice", (*dit)->getName()); @@ -207,8 +207,7 @@ TEST_F(ProcessAssetTest, should_parse_a_process_archetype) ASSERT_EQ("device3", (*dit)->get("deviceUuid")); } } - - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); @@ -251,33 +250,33 @@ TEST_F(ProcessAssetTest, process_archetype_can_have_multiple_routings) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(2, routings->size()); - + auto it = routings->begin(); { auto routing = *it; ASSERT_EQ("routng1", routing->get("routingId")); ASSERT_EQ(1, routing->get("precedence")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); - + auto step = processSteps.front(); ASSERT_EQ("10", step->get("stepId")); ASSERT_EQ("Process Step 10", step->get("Description")); - + auto st = step->get("StartTime"); ASSERT_EQ("2025-11-24T00:00:00Z", getCurrentTime(st, GMT)); ASSERT_EQ(23000, step->get("Duration")); @@ -287,23 +286,23 @@ TEST_F(ProcessAssetTest, process_archetype_can_have_multiple_routings) auto routing = *it; ASSERT_EQ("routng2", routing->get("routingId")); ASSERT_EQ(2, routing->get("precedence")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); - + auto step = processSteps.front(); ASSERT_EQ("11", step->get("stepId")); ASSERT_EQ("Process Step 11", step->get("Description")); - + auto st = step->get("StartTime"); ASSERT_EQ("2025-11-25T00:00:00Z", getCurrentTime(st, GMT)); ASSERT_EQ(20000, step->get("Duration")); } - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -323,27 +322,27 @@ TEST_F(ProcessAssetTest, process_steps_can_be_optional) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(1, routings->size()); - + auto routing = routings->front(); ASSERT_EQ("routng1", routing->get("routingId")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); auto step = processSteps.front(); - + ASSERT_EQ("10", step->get("stepId")); ASSERT_EQ(5, step->get("sequence")); ASSERT_EQ(true, step->get("optional")); @@ -351,7 +350,7 @@ TEST_F(ProcessAssetTest, process_steps_can_be_optional) // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -369,15 +368,16 @@ TEST_F(ProcessAssetTest, process_archetype_must_have_at_least_one_routing) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(1, errors.size()); - auto error = dynamic_cast(errors.front().get()); - - ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + auto error = dynamic_cast(errors.front().get()); + + ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, + error->what()); ASSERT_EQ("ProcessArchetype", error->getEntity()); ASSERT_EQ("Routings", error->getProperty()); } @@ -399,22 +399,23 @@ TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(5, errors.size()); - + auto it = errors.begin(); { - auto error = dynamic_cast(it->get()); + auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, error->what()); + EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, + error->what()); EXPECT_EQ("Routing", error->getEntity()); EXPECT_EQ("ProcessStep", error->getProperty()); } - + it++; { auto error = it->get(); @@ -422,12 +423,14 @@ TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) EXPECT_EQ("Routings: Invalid element 'Routing'"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); } - + it++; { - auto error = dynamic_cast(it->get()); + auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 found"s, error->what()); + EXPECT_EQ( + "Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 found"s, + error->what()); EXPECT_EQ("Routings", error->getEntity()); EXPECT_EQ("Routing", error->getProperty()); } @@ -442,13 +445,13 @@ TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) it++; { - auto error = dynamic_cast(it->get()); + auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, error->what()); + EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, + error->what()); EXPECT_EQ("ProcessArchetype", error->getEntity()); EXPECT_EQ("Routings", error->getProperty()); } - } TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) @@ -473,20 +476,20 @@ TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto routings = asset->getList("Routings"); ASSERT_TRUE(routings); ASSERT_EQ(1, routings->size()); - + auto routing = routings->front(); ASSERT_EQ("routng1", routing->get("routingId")); - + auto processSteps = routing->get("ProcessStep"); ASSERT_EQ(1, processSteps.size()); auto step = processSteps.front(); @@ -494,14 +497,14 @@ TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) auto activityGroups = step->getList("ActivityGroups"); ASSERT_TRUE(activityGroups); ASSERT_EQ(1, activityGroups->size()); - + auto activityGroup = activityGroups->front(); ASSERT_EQ("act1", activityGroup->get("activityGroupId")); ASSERT_EQ("fred", activityGroup->get("name")); - + auto activities = activityGroup->get("Activity"); ASSERT_EQ(1, activities.size()); - + auto activity = activities.front(); ASSERT_EQ("a1", activity->get("activityId")); ASSERT_EQ(2, activity->get("sequence")); @@ -512,7 +515,7 @@ TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -554,15 +557,15 @@ TEST_F(ProcessAssetTest, should_generate_json) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + entity::JsonEntityPrinter jprinter(2, true); - + auto sdoc = jprinter.print(entity); EXPECT_EQ(R"({ "ProcessArchetype": { @@ -641,7 +644,8 @@ TEST_F(ProcessAssetTest, should_generate_json) "assetId": "PROCESS_ARCH_ID", "revision": "1" } -})", sdoc); +})", + sdoc); } TEST_F(ProcessAssetTest, should_parse_and_generate_a_process) @@ -681,17 +685,17 @@ TEST_F(ProcessAssetTest, should_parse_and_generate_a_process) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -730,22 +734,24 @@ TEST_F(ProcessAssetTest, process_can_only_have_one_routings) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(3, errors.size()); - + auto it = errors.begin(); { - auto error = dynamic_cast(it->get()); + auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more than 1 entries, 2 found"s, error->what()); + EXPECT_EQ( + "Routings(Routing): Entity list requirement Routing must have at least 1 and no more than 1 entries, 2 found"s, + error->what()); EXPECT_EQ("Routings", error->getEntity()); EXPECT_EQ("Routing", error->getProperty()); } - + it++; { auto error = it->get(); @@ -756,7 +762,7 @@ TEST_F(ProcessAssetTest, process_can_only_have_one_routings) it++; { - auto error = dynamic_cast(it->get()); + auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); EXPECT_EQ("Process(Routings): Property Routings is required and not provided"s, error->what()); EXPECT_EQ("Process", error->getEntity()); diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index 06b03ef15..d574d3bca 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -57,10 +57,7 @@ int main(int argc, char *argv[]) class TargetTest : public testing::Test { protected: - void SetUp() override - { - m_writer = make_unique(true); - } + void SetUp() override { m_writer = make_unique(true); } void TearDown() override { m_writer.reset(); } @@ -76,11 +73,10 @@ TEST_F(TargetTest, simple_target_device) )DOC"; - + auto root = make_shared(); - auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} - }); + auto tf = make_shared( + Requirements {{"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false}}); root->registerFactory("Root", tf); ErrorList errors; @@ -89,7 +85,7 @@ TEST_F(TargetTest, simple_target_device) auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + auto targets = entity->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(1, targets->size()); @@ -112,42 +108,41 @@ TEST_F(TargetTest, target_device_and_device_group) )DOC"; - + auto root = make_shared(); - auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} - }); + auto tf = make_shared( + Requirements {{"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + auto targets = entity->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(2, targets->size()); auto it = targets->begin(); - + ASSERT_EQ("TargetDevice", (*it)->getName()); ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); - + it++; auto group = *it; - + ASSERT_EQ("TargetGroup", group->getName()); ASSERT_EQ("group_id", group->get("groupId")); ASSERT_TRUE(group->hasProperty("LIST")); const auto groupTargets = group->getListProperty(); ASSERT_EQ(2, groupTargets.size()); - + auto git = groupTargets.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); ASSERT_EQ("device-5678", (*git)->get("deviceUuid")); - + git++; ASSERT_EQ("TargetDevice", (*git)->getName()); ASSERT_EQ("device-9999", (*git)->get("deviceUuid")); @@ -166,20 +161,19 @@ TEST_F(TargetTest, target_device_and_device_group_json) )DOC"; - + auto root = make_shared(); - auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} - }); + auto tf = make_shared( + Requirements {{"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + entity::JsonEntityPrinter jsonPrinter(2, true); auto json = jsonPrinter.print(entity); @@ -206,7 +200,8 @@ TEST_F(TargetTest, target_device_and_device_group_json) ] } } -})JSON", json); +})JSON", + json); } TEST_F(TargetTest, nested_target_groups_with_target_refs) @@ -226,58 +221,57 @@ TEST_F(TargetTest, nested_target_groups_with_target_refs) )DOC"; - + auto root = make_shared(); - auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} - }); + auto tf = make_shared( + Requirements {{"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + auto targets = entity->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(3, targets->size()); auto it = targets->begin(); - + ASSERT_EQ("TargetDevice", (*it)->getName()); ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); - + it++; auto group = *it; - + ASSERT_EQ("TargetGroup", group->getName()); ASSERT_EQ("A", group->get("groupId")); - + ASSERT_TRUE(group->hasProperty("LIST")); const auto groupTargets = group->getListProperty(); ASSERT_EQ(2, groupTargets.size()); - + auto git = groupTargets.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); ASSERT_EQ("device-5678", (*git)->get("deviceUuid")); - + git++; ASSERT_EQ("TargetDevice", (*git)->getName()); ASSERT_EQ("device-9999", (*git)->get("deviceUuid")); - + group = *(++it); ASSERT_EQ("TargetGroup", group->getName()); ASSERT_EQ("B", group->get("groupId")); - + ASSERT_TRUE(group->hasProperty("LIST")); const auto groupTargets2 = group->getListProperty(); ASSERT_EQ(2, groupTargets2.size()); - + git = groupTargets2.begin(); ASSERT_EQ("TargetDevice", (*git)->getName()); ASSERT_EQ("device-2222", (*git)->get("deviceUuid")); - + git++; ASSERT_EQ("TargetRef", (*git)->getName()); ASSERT_EQ("A", (*git)->get("groupIdRef")); @@ -295,25 +289,24 @@ TEST_F(TargetTest, reject_empty_groups) )DOC"; - + auto root = make_shared(); - auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false} - }); + auto tf = make_shared( + Requirements {{"Targets", ValueType::ENTITY_LIST, Target::getDeviceTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(2, errors.size()); ASSERT_TRUE(entity); - + auto targets = entity->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(1, targets->size()); auto it = targets->begin(); - + ASSERT_EQ("TargetDevice", (*it)->getName()); ASSERT_EQ("device-1234", (*it)->get("deviceUuid")); } @@ -332,50 +325,49 @@ TEST_F(TargetTest, verify_target_requirement) )DOC"; - + auto root = make_shared(); auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false} - }); + {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + auto targets = entity->getList("Targets"); ASSERT_TRUE(targets); ASSERT_EQ(1, targets->size()); auto it = targets->begin(); - + ASSERT_EQ("TargetRequirement", (*it)->getName()); ASSERT_EQ("req1", (*it)->get("requirementId")); - + ASSERT_TRUE((*it)->hasProperty("CapabilityTable")); const auto table = (*it)->get("CapabilityTable"); - + ASSERT_EQ(2, table.size()); - + auto rowIt = table.begin(); ASSERT_EQ("R1", rowIt->m_key); ASSERT_TRUE(holds_alternative(rowIt->m_value)); auto &row = get(rowIt->m_value); ASSERT_EQ(1, row.size()); - + auto cellIt = row.begin(); ASSERT_EQ("C1", cellIt->m_key); ASSERT_TRUE(holds_alternative(cellIt->m_value)); ASSERT_EQ("ABC", get(cellIt->m_value)); - + rowIt++; ASSERT_EQ("R2", rowIt->m_key); ASSERT_TRUE(holds_alternative(rowIt->m_value)); auto &row2 = get(rowIt->m_value); ASSERT_EQ(1, row2.size()); - + cellIt = row2.begin(); ASSERT_EQ("C2", cellIt->m_key); ASSERT_TRUE(holds_alternative(cellIt->m_value)); @@ -396,24 +388,22 @@ TEST_F(TargetTest, verify_target_requirement_in_json) )DOC"; - - + auto root = make_shared(); auto tf = make_shared(Requirements { - {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false} - }); + {"Targets", ValueType::ENTITY_LIST, Target::getRequirementTargetsFactory(), false}}); root->registerFactory("Root", tf); - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(root, doc, errors); ASSERT_EQ(0, errors.size()); ASSERT_TRUE(entity); - + entity::JsonEntityPrinter jsonPrinter(2, true); auto json = jsonPrinter.print(entity); - + ASSERT_EQ(R"JSON({ "Root": { "Targets": { @@ -432,6 +422,6 @@ TEST_F(TargetTest, verify_target_requirement_in_json) ] } } -})JSON", json); - +})JSON", + json); } From 9b74c0b4d2bdec68e15e6d882326c4d310afc3f6 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Mon, 1 Dec 2025 16:44:42 +0100 Subject: [PATCH 56/84] Finished part and part archetype implementations --- src/mtconnect/asset/part.cpp | 28 +- test_package/part_test.cpp | 703 +++++++++++++++++------------------ 2 files changed, 360 insertions(+), 371 deletions(-) diff --git a/src/mtconnect/asset/part.cpp b/src/mtconnect/asset/part.cpp index 7257dd305..b2fb009dd 100644 --- a/src/mtconnect/asset/part.cpp +++ b/src/mtconnect/asset/part.cpp @@ -24,6 +24,11 @@ namespace mtconnect { namespace asset { FactoryPtr PartArchetype::getFactory() { + auto ext = make_shared(); + ext->registerFactory(regex(".+"), ext); + ext->setAny(true); + ext->setList(true); + static FactoryPtr factory; if (!factory) { @@ -42,8 +47,11 @@ namespace mtconnect { {"revision", true}, {"family", false}, {"drawing", false}, - {"Customers", ValueType::ENTITY_LIST, customers, true}, + {"Customers", ValueType::ENTITY_LIST, customers, false}, }); + factory->registerFactory(regex(".+"), ext); + factory->setAny(true); + } return factory; } @@ -63,22 +71,26 @@ namespace mtconnect { static FactoryPtr factory; if (!factory) { + auto ext = make_shared(); + ext->registerFactory(regex(".+"), ext); + ext->setAny(true); + ext->setList(true); + auto identifier = make_shared(Requirements { - {"type", true}, {"stepIdRef", false}, {"timestamp", ValueType::TIMESTAMP, true}}); + {"type", ControlledVocab {"UNIQUE_IDENTIFIER", "GROUP_IDENTIFIER"}, true}, + {"stepIdRef", false}, {"timestamp", ValueType::TIMESTAMP, true}, {"VALUE", true}}); auto identifiers = make_shared(Requirements { - {"UniqueIdentitier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}, - {"GroupIdentifier", ValueType::ENTITY, identifier, 0, entity::Requirement::Infinite}}); - - identifiers->setMinListSize(1); - identifiers->registerMatchers(); + {"Identifier", ValueType::ENTITY, identifier, 1, entity::Requirement::Infinite}}); factory = make_shared(*Asset::getFactory()); factory->addRequirements({{"revision", true}, {"family", false}, {"drawing", false}, {"PartIdentifiers", ValueType::ENTITY_LIST, identifiers, false}}); - } + factory->registerFactory(regex(".+"), ext); + factory->setAny(true); + } return factory; } diff --git a/test_package/part_test.cpp b/test_package/part_test.cpp index 3611d6a4d..ada16dfca 100644 --- a/test_package/part_test.cpp +++ b/test_package/part_test.cpp @@ -67,6 +67,8 @@ class PartAssetTest : public testing::Test std::unique_ptr m_agentTestHelper; }; +/// @section PartArchetype tests + TEST_F(PartAssetTest, should_parse_a_part_archetype) { const auto doc = @@ -197,21 +199,10 @@ TEST_F(PartAssetTest, process_archetype_can_have_multiple_customers) ASSERT_EQ(content, doc); } -/* -TEST_F(PartAssetTest, process_steps_can_be_optional) +TEST_F(PartAssetTest, customers_are_optional) { const auto doc = - R"DOC( - - - - Process Step 10 - 2025-11-24T00:00:00Z - 23000 - - - - + R"DOC( )DOC"; ErrorList errors; @@ -223,20 +214,11 @@ TEST_F(PartAssetTest, process_steps_can_be_optional) auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - auto routings = asset->getList("Routings"); - ASSERT_TRUE(routings); - ASSERT_EQ(1, routings->size()); - - auto routing = routings->front(); - ASSERT_EQ("routng1", routing->get("routingId")); - - auto processSteps = routing->get("ProcessStep"); - ASSERT_EQ(1, processSteps.size()); - auto step = processSteps.front(); - - ASSERT_EQ("10", step->get("stepId")); - ASSERT_EQ(5, step->get("sequence")); - ASSERT_EQ(true, step->get("optional")); + ASSERT_EQ("PartArchetype", asset->getName()); + ASSERT_EQ("PART1234", asset->getAssetId()); + ASSERT_EQ("5", asset->get("revision")); + ASSERT_EQ("STEP222", asset->get("drawing")); + ASSERT_EQ("HHH", asset->get("family")); // Round trip test entity::XmlPrinter printer; @@ -246,414 +228,409 @@ TEST_F(PartAssetTest, process_steps_can_be_optional) ASSERT_EQ(content, doc); } -TEST_F(PartAssetTest, process_archetype_must_have_at_least_one_routing) +TEST_F(PartAssetTest, should_generate_json) { const auto doc = - R"DOC( - - - - - - - - + R"DOC( + + + + + + + + +
100 Fruitstand Rd, Ork Arkansas, 11111
+ Some customer +
+
+
)DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); - ASSERT_EQ(1, errors.size()); - auto error = dynamic_cast(errors.front().get()); - - ASSERT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, -error->what()); ASSERT_EQ("ProcessArchetype", error->getEntity()); ASSERT_EQ("Routings", -error->getProperty()); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + ASSERT_EQ("PartArchetype", asset->getName()); + + // Round trip test + entity::JsonEntityPrinter jprinter(2, true); + + auto jdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "PartArchetype": { + "Configuration": { + "Relationships": { + "AssetRelationship": [ + { + "assetIdRef": "MATERIAL", + "assetType": "RawMaterial", + "id": "A", + "type": "PEER" + }, + { + "assetIdRef": "PROCESS", + "assetType": "ProcessArchetype", + "id": "B", + "type": "PEER" + } + ] + } + }, + "Customers": { + "Customer": [ + { + "Address": "100 Fruitstand Rd, Ork Arkansas, 11111", + "Description": "Some customer", + "customerId": "C00241", + "name": "customer name" + } + ] + }, + "assetId": "PART1234", + "drawing": "STEP222", + "family": "HHH", + "revision": "5" + } +})", jdoc); } -TEST_F(PartAssetTest, process_archetype_routing_must_have_a_process_step) +TEST_F(PartAssetTest, part_archetype_should_be_extensible) { const auto doc = - R"DOC( - - - - - - - - - - - - + R"DOC( + + + + + + + + +
100 Fruitstand Rd, Ork Arkansas, 11111
+ Some customer +
+
+ + + + + Some simple extension value +
)DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); - ASSERT_EQ(5, errors.size()); - - auto it = errors.begin(); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + ASSERT_EQ("PartArchetype", asset->getName()); + + auto properties = asset->getList("Properties"); + ASSERT_TRUE(properties); + + ASSERT_EQ(2, properties->size()); { - auto error = dynamic_cast(it->get()); - ASSERT_TRUE(error); - EXPECT_EQ("Routing(ProcessStep): Property ProcessStep is required and not provided"s, -error->what()); EXPECT_EQ("Routing", error->getEntity()); EXPECT_EQ("ProcessStep", -error->getProperty()); + auto it = properties->begin(); + EXPECT_EQ("CustomProperty1", (*it)->get("name")); + EXPECT_EQ("Value1", (*it)->get("value")); + it++; + EXPECT_EQ("CustomProperty2", (*it)->get("name")); + EXPECT_EQ("Value2", (*it)->get("value")); } - it++; - { - auto error = it->get(); - ASSERT_TRUE(error); - EXPECT_EQ("Routings: Invalid element 'Routing'"s, error->what()); - EXPECT_EQ("Routings", error->getEntity()); - } + + auto sext = asset->get("SimpleExtension"); + ASSERT_EQ("Some simple extension value", sext); +} - it++; - { - auto error = dynamic_cast(it->get()); - ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 entries, 0 -found"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); EXPECT_EQ("Routing", -error->getProperty()); - } +/// @section Part asset tests - it++; +TEST_F(PartAssetTest, should_parse_a_part) +{ + const auto doc = + R"DOC( + + + + + + + + UID123456 + GID1235 + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + ASSERT_EQ("Part", asset->getName()); + EXPECT_EQ("PART1234", asset->getAssetId()); + EXPECT_EQ("5", asset->get("revision")); + EXPECT_EQ("STEP222", asset->get("drawing")); + EXPECT_EQ("HHH", asset->get("family")); + EXPECT_EQ("NATIVE001", asset->get("nativeId")); + + auto configuration = asset->get("Configuration"); + ASSERT_TRUE(configuration); + + auto relationships = configuration->getList("Relationships"); + ASSERT_TRUE(relationships); + ASSERT_EQ(2, relationships->size()); + { - auto error = it->get(); - ASSERT_TRUE(error); - EXPECT_EQ("ProcessArchetype: Invalid element 'Routings'"s, error->what()); - EXPECT_EQ("ProcessArchetype", error->getEntity()); + auto it = relationships->begin(); + EXPECT_EQ("A", (*it)->get("id")); + EXPECT_EQ("MATERIAL", (*it)->get("assetIdRef")); + EXPECT_EQ("PEER", (*it)->get("type")); + EXPECT_EQ("RawMaterial", (*it)->get("assetType")); + + it++; + EXPECT_EQ("B", (*it)->get("id")); + EXPECT_EQ("PROCESS", (*it)->get("assetIdRef")); + EXPECT_EQ("PEER", (*it)->get("type")); + EXPECT_EQ("ProcessArchetype", (*it)->get("assetType")); } - - it++; + + auto identifiers = asset->getList("PartIdentifiers"); + ASSERT_TRUE(identifiers); + ASSERT_EQ(2, identifiers->size()); + { - auto error = dynamic_cast(it->get()); - ASSERT_TRUE(error); - EXPECT_EQ("ProcessArchetype(Routings): Property Routings is required and not provided"s, -error->what()); EXPECT_EQ("ProcessArchetype", error->getEntity()); EXPECT_EQ("Routings", -error->getProperty()); + auto it = identifiers->begin(); + auto identifier = *it; + EXPECT_EQ("UNIQUE_IDENTIFIER", identifier->get("type")); + EXPECT_EQ("10", identifier->get("stepIdRef")); + auto st = identifier->get("timestamp"); + EXPECT_EQ("2025-11-28T00:01:00Z", getCurrentTime(st, GMT)); + EXPECT_EQ("UID123456", identifier->getValue()); + + it++; + identifier = *it; + EXPECT_EQ("GROUP_IDENTIFIER", identifier->get("type")); + EXPECT_EQ("11", identifier->get("stepIdRef")); + st = identifier->get("timestamp"); + EXPECT_EQ("2025-11-28T00:02:00Z", getCurrentTime(st, GMT)); + EXPECT_EQ("GID1235", identifier->getValue()); } - + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); } -TEST_F(PartAssetTest, activity_can_have_a_sequence_precidence_and_be_options) +TEST_F(PartAssetTest, part_identifiers_are_optional) { const auto doc = - R"DOC( - - - - - - - First Activity - - - - - - - + R"DOC( )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - - auto routings = asset->getList("Routings"); - ASSERT_TRUE(routings); - ASSERT_EQ(1, routings->size()); - - auto routing = routings->front(); - ASSERT_EQ("routng1", routing->get("routingId")); - - auto processSteps = routing->get("ProcessStep"); - ASSERT_EQ(1, processSteps.size()); - auto step = processSteps.front(); - - auto activityGroups = step->getList("ActivityGroups"); - ASSERT_TRUE(activityGroups); - ASSERT_EQ(1, activityGroups->size()); - - auto activityGroup = activityGroups->front(); - ASSERT_EQ("act1", activityGroup->get("activityGroupId")); - ASSERT_EQ("fred", activityGroup->get("name")); - - auto activities = activityGroup->get("Activity"); - ASSERT_EQ(1, activities.size()); - - auto activity = activities.front(); - ASSERT_EQ("a1", activity->get("activityId")); - ASSERT_EQ(2, activity->get("sequence")); - ASSERT_EQ("First Activity", activity->get("Description")); - ASSERT_EQ(true, activity->get("optional")); - ASSERT_EQ(3, activity->get("precedence")); - + + ASSERT_EQ("Part", asset->getName()); + EXPECT_EQ("PART1234", asset->getAssetId()); + EXPECT_EQ("5", asset->get("revision")); + EXPECT_EQ("STEP222", asset->get("drawing")); + EXPECT_EQ("HHH", asset->get("family")); + EXPECT_EQ("NATIVE001", asset->get("nativeId")); + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } -TEST_F(PartAssetTest, should_generate_json) +TEST_F(PartAssetTest, part_identifiers_type_must_be_unique_or_group) { const auto doc = - R"DOC( - - - - - - - - - Process Step 10 - 2025-11-24T00:00:00Z - 23000 - - - - - - - First Activity - - - - - - - - - - - - - - + R"DOC( + + UID123456 + GID1235 + + )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); - ASSERT_EQ(0, errors.size()); - - entity::JsonEntityPrinter jprinter(2, true); - - auto sdoc = jprinter.print(entity); - EXPECT_EQ(R"({ - "ProcessArchetype": { - "Configuration": { - "Relationships": { - "AssetRelationship": [ - { - "assetIdRef": "PART_ID", - "assetType": "PART_ARCHETYPE", - "id": "reference_id", - "type": "PEER" - } - ] - } - }, - "Routings": { - "Routing": [ - { - "ProcessStep": [ - { - "ActivityGroups": { - "ActivityGroup": [ - { - "Activity": [ - { - "Description": "First Activity", - "activityId": "a1", - "optional": true, - "precedence": 2, - "sequence": 1 - } - ], - "activityGroupId": "act1", - "name": "fred" - } - ] - }, - "Description": "Process Step 10", - "Duration": 23000.0, - "StartTime": "2025-11-24T00:00:00Z", - "Targets": { - "TargetRef": [ - { - "groupIdRef": "group1" - } - ] - }, - "stepId": "10" - } - ], - "precedence": 1, - "routingId": "routng1" - } - ] - }, - "Targets": { - "TargetDevice": [ - { - "targetId": "device1" - } - ], - "TargetGroup": [ - { - "TargetDevice": [ - { - "targetId": "device2" - }, - { - "targetId": "device3" - } - ], - "groupId": "group1" - } - ] - }, - "assetId": "PROCESS_ARCH_ID", - "revision": "1" + ASSERT_EQ(2, errors.size()); + + auto it = errors.begin(); + { + auto error = dynamic_cast(it->get()); + ASSERT_TRUE(error); + EXPECT_EQ( + "Identifier(type): Invalid value for 'type': 'OTHER_IDENTIFIER' is not allowed"s, + error->what()); + EXPECT_EQ("Identifier", error->getEntity()); + EXPECT_EQ("type", error->getProperty()); } -})", sdoc); + + it++; + { + auto error = it->get(); + ASSERT_TRUE(error); + EXPECT_EQ("PartIdentifiers: Invalid element 'Identifier'"s, error->what()); + EXPECT_EQ("PartIdentifiers", error->getEntity()); + } + } -TEST_F(PartAssetTest, should_parse_and_generate_a_process) +TEST_F(PartAssetTest, part_should_be_extensible) { const auto doc = - R"DOC( + R"DOC( - + + - - - - Process Step 10 - 2025-11-24T00:00:00Z - 23000 - - - - - - - First Activity - - - - - - - - - - - - - - + + UID123456 + GID1235 + + + 2025-12-01T00:00:00Z + 2025-12-20T00:00:00Z + 100 + + )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - - // Round trip test - entity::XmlPrinter printer; - printer.print(*m_writer, entity, {}); - - string content = m_writer->getContent(); - ASSERT_EQ(content, doc); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto workOrder = asset->get("WorkOrder"); + ASSERT_TRUE(workOrder); + + ASSERT_EQ("WO12345", workOrder->get("number")); + ASSERT_EQ("2025-12-01T00:00:00Z", workOrder->get("OrderDate")); + ASSERT_EQ("2025-12-20T00:00:00Z", workOrder->get("DueDate")); + ASSERT_EQ("100", workOrder->get("PlannedQuantity")); } -TEST_F(PartAssetTest, process_can_only_have_one_routings) +TEST_F(PartAssetTest, part_should_generate_json) { const auto doc = - R"DOC( + R"DOC( - + + - - - - Process Step 10 - 2025-11-24T00:00:00Z - 23000 - - - - - Process Step 11 - 2025-11-25T00:00:00Z - 20000 - - - - - - - - - - - + + UID123456 + GID1235 + + + 2025-12-01T00:00:00Z + 2025-12-20T00:00:00Z + 100 + + )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); - ASSERT_EQ(3, errors.size()); - - auto it = errors.begin(); - { - auto error = dynamic_cast(it->get()); - ASSERT_TRUE(error); - EXPECT_EQ("Routings(Routing): Entity list requirement Routing must have at least 1 and no more -than 1 entries, 2 found"s, error->what()); EXPECT_EQ("Routings", error->getEntity()); - EXPECT_EQ("Routing", error->getProperty()); - } - - it++; - { - auto error = it->get(); - ASSERT_TRUE(error); - EXPECT_EQ("Process: Invalid element 'Routings'"s, error->what()); - EXPECT_EQ("Process", error->getEntity()); - } - - it++; - { - auto error = dynamic_cast(it->get()); - ASSERT_TRUE(error); - EXPECT_EQ("Process(Routings): Property Routings is required and not provided"s, error->what()); - EXPECT_EQ("Process", error->getEntity()); - EXPECT_EQ("Routings", error->getProperty()); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + // Round trip test + entity::JsonEntityPrinter jprinter(2, true); + + auto jdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "Part": { + "Configuration": { + "Relationships": { + "AssetRelationship": [ + { + "assetIdRef": "MATERIAL", + "assetType": "RawMaterial", + "id": "A", + "type": "PEER" + }, + { + "assetIdRef": "PROCESS", + "assetType": "ProcessArchetype", + "id": "B", + "type": "PEER" + } + ] + } + }, + "PartIdentifiers": { + "Identifier": [ + { + "value": "UID123456", + "stepIdRef": "10", + "timestamp": "2025-11-28T00:01:00Z", + "type": "UNIQUE_IDENTIFIER" + }, + { + "value": "GID1235", + "stepIdRef": "11", + "timestamp": "2025-11-28T00:02:00Z", + "type": "GROUP_IDENTIFIER" + } + ] + }, + "WorkOrder": { + "DueDate": "2025-12-20T00:00:00Z", + "OrderDate": "2025-12-01T00:00:00Z", + "PlannedQuantity": "100", + "number": "WO12345" + }, + "assetId": "PART1234", + "drawing": "STEP222", + "family": "HHH", + "nativeId": "NATIVE001", + "revision": "5" } +})", jdoc); + } -*/ + From 2f7b2dc9d9fec247f25c9086fb8bb92143bd924e Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 3 Dec 2025 16:20:14 +0100 Subject: [PATCH 57/84] First version of task archetype test synced with model --- agent_lib/CMakeLists.txt | 2 + src/mtconnect/agent.cpp | 3 + src/mtconnect/asset/part.cpp | 23 +-- src/mtconnect/asset/task.cpp | 143 ++++++++++++++++++ src/mtconnect/asset/task.hpp | 44 ++++++ test_package/CMakeLists.txt | 1 + test_package/part_test.cpp | 103 ++++++------- test_package/task_test.cpp | 284 +++++++++++++++++++++++++++++++++++ 8 files changed, 539 insertions(+), 64 deletions(-) create mode 100644 src/mtconnect/asset/task.cpp create mode 100644 src/mtconnect/asset/task.hpp create mode 100644 test_package/task_test.cpp diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index ade1e8807..4af4e1b36 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -31,6 +31,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/process.hpp" "${SOURCE_DIR}/asset/pallet.hpp" "${SOURCE_DIR}/asset/target.hpp" + "${SOURCE_DIR}/asset/task.hpp" # src/asset SOURCE_FILES_ONLY @@ -46,6 +47,7 @@ set(AGENT_SOURCES "${SOURCE_DIR}/asset/process.cpp" "${SOURCE_DIR}/asset/pallet.cpp" "${SOURCE_DIR}/asset/target.cpp" + "${SOURCE_DIR}/asset/task.cpp" # src/buffer HEADER_FILES_ONLY diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index b9162f65d..f34205c1a 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -52,6 +52,7 @@ #include "mtconnect/asset/process.hpp" #include "mtconnect/asset/qif_document.hpp" #include "mtconnect/asset/raw_material.hpp" +#include "mtconnect/asset/task.hpp" #include "mtconnect/configuration/config_options.hpp" #include "mtconnect/device_model/agent_device.hpp" #include "mtconnect/entity/xml_parser.hpp" @@ -108,6 +109,8 @@ namespace mtconnect { ProcessArchetype::registerAsset(); Part::registerAsset(); PartArchetype::registerAsset(); + Task::registerAsset(); + TaskArchetype::registerAsset(); m_assetStorage = make_unique( GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); diff --git a/src/mtconnect/asset/part.cpp b/src/mtconnect/asset/part.cpp index b2fb009dd..ac642438c 100644 --- a/src/mtconnect/asset/part.cpp +++ b/src/mtconnect/asset/part.cpp @@ -24,14 +24,14 @@ namespace mtconnect { namespace asset { FactoryPtr PartArchetype::getFactory() { - auto ext = make_shared(); - ext->registerFactory(regex(".+"), ext); - ext->setAny(true); - ext->setList(true); - static FactoryPtr factory; if (!factory) { + auto ext = make_shared(); + ext->registerFactory(regex(".+"), ext); + ext->setAny(true); + ext->setList(true); + auto customer = make_shared(Requirements { {"customerId", true}, {"name", false}, @@ -51,7 +51,6 @@ namespace mtconnect { }); factory->registerFactory(regex(".+"), ext); factory->setAny(true); - } return factory; } @@ -76,12 +75,14 @@ namespace mtconnect { ext->setAny(true); ext->setList(true); - auto identifier = make_shared(Requirements { - {"type", ControlledVocab {"UNIQUE_IDENTIFIER", "GROUP_IDENTIFIER"}, true}, - {"stepIdRef", false}, {"timestamp", ValueType::TIMESTAMP, true}, {"VALUE", true}}); + auto identifier = make_shared( + Requirements {{"type", ControlledVocab {"UNIQUE_IDENTIFIER", "GROUP_IDENTIFIER"}, true}, + {"stepIdRef", false}, + {"timestamp", ValueType::TIMESTAMP, true}, + {"VALUE", true}}); auto identifiers = make_shared(Requirements { - {"Identifier", ValueType::ENTITY, identifier, 1, entity::Requirement::Infinite}}); + {"Identifier", ValueType::ENTITY, identifier, 1, entity::Requirement::Infinite}}); factory = make_shared(*Asset::getFactory()); factory->addRequirements({{"revision", true}, @@ -90,7 +91,7 @@ namespace mtconnect { {"PartIdentifiers", ValueType::ENTITY_LIST, identifiers, false}}); factory->registerFactory(regex(".+"), ext); factory->setAny(true); - } + } return factory; } diff --git a/src/mtconnect/asset/task.cpp b/src/mtconnect/asset/task.cpp new file mode 100644 index 000000000..8d1461651 --- /dev/null +++ b/src/mtconnect/asset/task.cpp @@ -0,0 +1,143 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +#include "task.hpp" + +#include "target.hpp" + +using namespace std; + +namespace mtconnect { + using namespace entity; + namespace asset { + FactoryPtr TaskArchetype::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + auto collaborator = make_shared(Requirements { + {"collaboratorId", true}, + {"type", false}, + {"optional", ValueType::BOOL, false}, + {"Targets", ValueType::ENTITY_LIST, Target::getAllTargetsFactory(), true}}); + + auto collaborators = make_shared( + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, 1, + entity::Requirement::Infinite}}); + auto coordinator = make_shared( + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); + + + auto ext = make_shared(); + ext->registerFactory(regex(".+"), ext); + ext->setAny(true); + ext->setList(true); + + auto subTaskRef = make_shared(Requirements {{"order", ValueType::INTEGER, true}, + {"parallel", ValueType::BOOL, false}, + {"optional", ValueType::BOOL, false}, + {"group", false}, + {"VALUE", true}}); + + auto subTaskRefs = make_shared(Requirements { + {"SubTaskRef", ValueType::ENTITY, subTaskRef, 1, entity::Requirement::Infinite}}); + + factory = make_shared(*Asset::getFactory()); + factory->addRequirements( + {{"TaskType", true}, + {"Priority", ValueType::INTEGER, false}, + {"Coordinator", ValueType::ENTITY, coordinator, true}, + {"Collaborators", ValueType::ENTITY_LIST, collaborators, + true}, + {"Targets", ValueType::ENTITY_LIST, Target::getAllTargetsFactory(), false}, + {"SubTaskRefs", ValueType::ENTITY_LIST, subTaskRefs, false}}); + factory->setOrder( + {{"TaskType", "Priority", "Targets", "Coordinator", "Collaborators", "SubTaskREfs"}}); + factory->registerFactory(regex(".+"), ext); + factory->setAny(true); + } + return factory; + } + + void TaskArchetype::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("TaskArchetype", getFactory()); + once = false; + } + } + + FactoryPtr Task::getFactory() + { + static FactoryPtr factory; + if (!factory) + { + auto collaborator = make_shared(Requirements { + {"collaboratorId", true}, + {"type", false}, + {"collaboratorDeviceUuid", false}, + {"requirementId", false}}); + + auto collaborators = make_shared( + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, 1, + entity::Requirement::Infinite}}); + auto coordinator = make_shared( + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); + + + auto ext = make_shared(); + ext->registerFactory(regex(".+"), ext); + ext->setAny(true); + ext->setList(true); + + factory = make_shared(*Asset::getFactory()); + factory->addRequirements( + {{"TaskType", true}, + {"State", + ControlledVocab {"INACTIVE", "PREPARING", "COMMITTING", "COMMITTED", "COMPLETE", + "FAIL"}, + true}, + {"ParentTaskAssetId", false}, + {"TaskArchetypeAssetId", false}, + {"Coordinator", ValueType::ENTITY, coordinator, true}, + {"Collaborators", ValueType::ENTITY_LIST, collaborators, + true}}); + auto task = make_shared( + Requirements {{"Task", ValueType::ENTITY, factory, 1, entity::Requirement::Infinite}}); + + factory->addRequirements({{"SubTasks", ValueType::ENTITY_LIST, task, false}}); + factory->setOrder({"TaskType", "State", "ParentTaskAssetId", "TaskArchetypeAssetId", + "SubTasks", "Coordinator", "Collaborators"}); + factory->registerFactory(regex(".+"), ext); + factory->setAny(true); + } + return factory; + } + + void Task::registerAsset() + { + static bool once {true}; + if (once) + { + Asset::registerAssetType("Task", getFactory()); + once = false; + } + } + } // namespace asset +} // namespace mtconnect diff --git a/src/mtconnect/asset/task.hpp b/src/mtconnect/asset/task.hpp new file mode 100644 index 000000000..3967dae59 --- /dev/null +++ b/src/mtconnect/asset/task.hpp @@ -0,0 +1,44 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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 "asset.hpp" +#include "mtconnect/entity/factory.hpp" +#include "mtconnect/utilities.hpp" + +namespace mtconnect::asset { + /// @brief Manufacturing process archetype asset + class AGENT_LIB_API TaskArchetype : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; + + /// @brief Manufacturing process asset + class AGENT_LIB_API Task : public Asset + { + public: + static entity::FactoryPtr getFactory(); + static void registerAsset(); + }; +} // namespace mtconnect::asset diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 547132d35..b93a366fa 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -224,6 +224,7 @@ add_agent_test(fixture FALSE asset) add_agent_test(target FALSE asset) add_agent_test(process FALSE asset) add_agent_test(part FALSE asset) +add_agent_test(task FALSE asset) add_agent_test(agent_device TRUE device_model) add_agent_test(component FALSE device_model) diff --git a/test_package/part_test.cpp b/test_package/part_test.cpp index ada16dfca..a7c44f791 100644 --- a/test_package/part_test.cpp +++ b/test_package/part_test.cpp @@ -246,21 +246,21 @@ TEST_F(PartAssetTest, should_generate_json)
)DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + ASSERT_EQ("PartArchetype", asset->getName()); - + // Round trip test entity::JsonEntityPrinter jprinter(2, true); - + auto jdoc = jprinter.print(entity); EXPECT_EQ(R"({ "PartArchetype": { @@ -297,7 +297,8 @@ TEST_F(PartAssetTest, should_generate_json) "family": "HHH", "revision": "5" } -})", jdoc); +})", + jdoc); } TEST_F(PartAssetTest, part_archetype_should_be_extensible) @@ -323,21 +324,21 @@ TEST_F(PartAssetTest, part_archetype_should_be_extensible) Some simple extension value )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + ASSERT_EQ("PartArchetype", asset->getName()); - + auto properties = asset->getList("Properties"); ASSERT_TRUE(properties); - + ASSERT_EQ(2, properties->size()); { auto it = properties->begin(); @@ -348,7 +349,6 @@ TEST_F(PartAssetTest, part_archetype_should_be_extensible) EXPECT_EQ("Value2", (*it)->get("value")); } - auto sext = asset->get("SimpleExtension"); ASSERT_EQ("Some simple extension value", sext); } @@ -371,48 +371,48 @@ TEST_F(PartAssetTest, should_parse_a_part) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + ASSERT_EQ("Part", asset->getName()); EXPECT_EQ("PART1234", asset->getAssetId()); EXPECT_EQ("5", asset->get("revision")); EXPECT_EQ("STEP222", asset->get("drawing")); EXPECT_EQ("HHH", asset->get("family")); EXPECT_EQ("NATIVE001", asset->get("nativeId")); - + auto configuration = asset->get("Configuration"); ASSERT_TRUE(configuration); - + auto relationships = configuration->getList("Relationships"); ASSERT_TRUE(relationships); ASSERT_EQ(2, relationships->size()); - + { auto it = relationships->begin(); EXPECT_EQ("A", (*it)->get("id")); EXPECT_EQ("MATERIAL", (*it)->get("assetIdRef")); EXPECT_EQ("PEER", (*it)->get("type")); EXPECT_EQ("RawMaterial", (*it)->get("assetType")); - + it++; EXPECT_EQ("B", (*it)->get("id")); EXPECT_EQ("PROCESS", (*it)->get("assetIdRef")); EXPECT_EQ("PEER", (*it)->get("type")); EXPECT_EQ("ProcessArchetype", (*it)->get("assetType")); } - + auto identifiers = asset->getList("PartIdentifiers"); ASSERT_TRUE(identifiers); ASSERT_EQ(2, identifiers->size()); - + { auto it = identifiers->begin(); auto identifier = *it; @@ -421,7 +421,7 @@ TEST_F(PartAssetTest, should_parse_a_part) auto st = identifier->get("timestamp"); EXPECT_EQ("2025-11-28T00:01:00Z", getCurrentTime(st, GMT)); EXPECT_EQ("UID123456", identifier->getValue()); - + it++; identifier = *it; EXPECT_EQ("GROUP_IDENTIFIER", identifier->get("type")); @@ -430,11 +430,11 @@ TEST_F(PartAssetTest, should_parse_a_part) EXPECT_EQ("2025-11-28T00:02:00Z", getCurrentTime(st, GMT)); EXPECT_EQ("GID1235", identifier->getValue()); } - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -444,27 +444,27 @@ TEST_F(PartAssetTest, part_identifiers_are_optional) const auto doc = R"DOC( )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + ASSERT_EQ("Part", asset->getName()); EXPECT_EQ("PART1234", asset->getAssetId()); EXPECT_EQ("5", asset->get("revision")); EXPECT_EQ("STEP222", asset->get("drawing")); EXPECT_EQ("HHH", asset->get("family")); EXPECT_EQ("NATIVE001", asset->get("nativeId")); - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } @@ -479,24 +479,23 @@ TEST_F(PartAssetTest, part_identifiers_type_must_be_unique_or_group) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(2, errors.size()); - + auto it = errors.begin(); { auto error = dynamic_cast(it->get()); ASSERT_TRUE(error); - EXPECT_EQ( - "Identifier(type): Invalid value for 'type': 'OTHER_IDENTIFIER' is not allowed"s, + EXPECT_EQ("Identifier(type): Invalid value for 'type': 'OTHER_IDENTIFIER' is not allowed"s, error->what()); EXPECT_EQ("Identifier", error->getEntity()); EXPECT_EQ("type", error->getProperty()); } - + it++; { auto error = it->get(); @@ -504,7 +503,6 @@ TEST_F(PartAssetTest, part_identifiers_type_must_be_unique_or_group) EXPECT_EQ("PartIdentifiers: Invalid element 'Identifier'"s, error->what()); EXPECT_EQ("PartIdentifiers", error->getEntity()); } - } TEST_F(PartAssetTest, part_should_be_extensible) @@ -528,19 +526,19 @@ TEST_F(PartAssetTest, part_should_be_extensible) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + auto workOrder = asset->get("WorkOrder"); ASSERT_TRUE(workOrder); - + ASSERT_EQ("WO12345", workOrder->get("number")); ASSERT_EQ("2025-12-01T00:00:00Z", workOrder->get("OrderDate")); ASSERT_EQ("2025-12-20T00:00:00Z", workOrder->get("DueDate")); @@ -568,19 +566,19 @@ TEST_F(PartAssetTest, part_should_generate_json) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); - + // Round trip test entity::JsonEntityPrinter jprinter(2, true); - + auto jdoc = jprinter.print(entity); EXPECT_EQ(R"({ "Part": { @@ -630,7 +628,6 @@ TEST_F(PartAssetTest, part_should_generate_json) "nativeId": "NATIVE001", "revision": "5" } -})", jdoc); - +})", + jdoc); } - diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp new file mode 100644 index 000000000..21f0d3b13 --- /dev/null +++ b/test_package/task_test.cpp @@ -0,0 +1,284 @@ +// +// Copyright Copyright 2009-2025, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// 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. +// + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include +#include +#include +#include +#include + +#include "agent_test_helper.hpp" +#include "json_helper.hpp" +#include "mtconnect/agent.hpp" +#include "mtconnect/asset/asset.hpp" +#include "mtconnect/asset/task.hpp" +#include "mtconnect/entity/xml_parser.hpp" +#include "mtconnect/entity/xml_printer.hpp" +#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/source/adapter/adapter.hpp" + +using json = nlohmann::json; +using namespace std; +using namespace mtconnect; +using namespace mtconnect::entity; +using namespace mtconnect::source::adapter; +using namespace mtconnect::asset; +using namespace mtconnect::printer; + +// main +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class TaskAssetTest : public testing::Test +{ +protected: + void SetUp() override + { // Create an agent with only 16 slots and 8 data items. + Task::registerAsset(); + TaskArchetype::registerAsset(); + m_writer = make_unique(true); + } + + void TearDown() override { m_writer.reset(); } + + std::unique_ptr m_writer; + std::unique_ptr m_agentTestHelper; +}; + +/// @section PartArchetype tests + +TEST_F(TaskAssetTest, should_parse_a_part_archetype) +{ + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + 1500 + + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + EXPECT_EQ("TaskArchetype", asset->getName()); + EXPECT_EQ("1aa7eece248093", asset->getAssetId()); + EXPECT_EQ("mxi_m001", asset->get("deviceUuid")); + + auto targets = asset->getList("Targets"); + ASSERT_TRUE(targets); + ASSERT_EQ(3, targets->size()); + + { + auto it = targets->begin(); + EXPECT_EQ("TargetDevice", (*it)->getName()); + EXPECT_EQ("Mazak123", (*it)->get("deviceUuid")); + + it++; + EXPECT_EQ("TargetDevice", (*it)->getName()); + EXPECT_EQ("Mazak456", (*it)->get("deviceUuid")); + + auto targetGroup = *++it; + EXPECT_EQ("TargetGroup", targetGroup->getName()); + EXPECT_EQ("MyRobots", targetGroup->get("groupId")); + + auto targetDevices = targetGroup->get("LIST"); + ASSERT_EQ(2, targetDevices.size()); + + { + auto dit = targetDevices.begin(); + EXPECT_EQ("TargetDevice", (*dit)->getName()); + EXPECT_EQ("UR123", (*dit)->get("deviceUuid")); + dit++; + + EXPECT_EQ("TargetDevice", (*dit)->getName()); + EXPECT_EQ("UR456", (*dit)->get("deviceUuid")); + } + } + + auto coordinator = asset->get("Coordinator"); + ASSERT_TRUE(coordinator); + EXPECT_EQ("Coordinator", coordinator->getName()); + auto collaborator = coordinator->get("Collaborator"); + ASSERT_TRUE(collaborator); + EXPECT_EQ("machine", collaborator->get("collaboratorId")); + EXPECT_EQ("CNC", collaborator->get("type")); + + auto collTargets = collaborator->getList("Targets"); + ASSERT_EQ(2, collTargets->size()); + { + auto it = collTargets->begin(); + EXPECT_EQ("TargetDevice", (*it)->getName()); + EXPECT_EQ("Mazak123", (*it)->get("deviceUuid")); + it++; + EXPECT_EQ("TargetDevice", (*it)->getName()); + EXPECT_EQ("Mazak456", (*it)->get("deviceUuid")); + } + + auto collaborators = asset->getList("Collaborators"); + ASSERT_EQ(2, collaborators->size()); + { + auto it = collaborators->begin(); + auto collab1 = *it; + EXPECT_EQ("Robot", collab1->get("collaboratorId")); + EXPECT_EQ("ROBOT", collab1->get("type")); + auto targets = collab1->getList("Targets"); + ASSERT_EQ(2, targets->size()); + { + auto tit = targets->begin(); + EXPECT_EQ("TargetRequirement", (*tit)->getName()); + EXPECT_EQ("ab", (*tit)->get("requirementId")); + + auto table = (*tit)->get("CapabilityTable"); + ASSERT_EQ(2, table.size()); + + const auto &row1 = get(table.find(DataSetEntry("PAYLOAD"))->m_value); + ASSERT_EQ(1, row1.size()); + EXPECT_EQ(1000, get(row1.find(TableCell("maximum"))->m_value)); + + const auto &row2 = get(table.find(DataSetEntry("REACH"))->m_value); + ASSERT_EQ(1, row2.size()); + EXPECT_EQ(1500, get(row2.find(TableCell("minimum"))->m_value)); + + tit++; + ASSERT_EQ("TargetRef", (*tit)->getName()); + ASSERT_EQ("MyRobots", (*tit)->get("groupIdRef")); + } + + it++; + auto collab2 = *it; + EXPECT_EQ("robot2", collab2->get("collaboratorId")); + EXPECT_EQ("ROBOT", collab2->get("type")); + + auto targets2 = collab2->getList("Targets"); + ASSERT_EQ(1, targets2->size()); + { + auto target = targets2->front(); + EXPECT_EQ("TargetDevice", target->getName()); + EXPECT_EQ("UR890", target->get("deviceUuid")); + } + } + + auto subtasks = asset->getList("SubTaskRefs"); + ASSERT_TRUE(subtasks); + ASSERT_EQ(2, subtasks->size()); + + { + auto it = subtasks->begin(); + EXPECT_EQ("SubTaskRef", (*it)->getName()); + EXPECT_EQ(1, (*it)->get("order")); + EXPECT_EQ("UnloadConv", (*it)->getValue()); + + it++; + EXPECT_EQ("SubTaskRef", (*it)->getName()); + EXPECT_EQ(2, (*it)->get("order")); + EXPECT_EQ("LoadCnc", (*it)->getValue()); + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(TaskAssetTest, task_archetype_should_produce_json) +{ + GTEST_SKIP(); +} + +TEST_F(TaskAssetTest, task_archetype_must_have_collaborators) +{ + GTEST_SKIP(); +} + +TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator) +{ + GTEST_SKIP(); +} + +TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_refs) +{ + GTEST_SKIP(); +} + +TEST_F(TaskAssetTest, should_parse_task) +{ + GTEST_SKIP(); +} + +TEST_F(TaskAssetTest, task_should_produce_json) +{ + GTEST_SKIP(); +} + + From b5fbe3609a935d9d9fe76d64144ce01d6c2fb9a0 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 08:13:30 +0100 Subject: [PATCH 58/84] Added support for table values in the parser. Changed TargetRequirement to a table without capablity --- src/mtconnect/asset/target.cpp | 6 +++--- src/mtconnect/entity/factory.hpp | 16 +++++++++++++++ src/mtconnect/entity/xml_parser.cpp | 5 +++++ test_package/target_test.cpp | 30 ++++++++++++----------------- test_package/task_test.cpp | 16 +++++++-------- 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp index 77880eccd..e9f9ba001 100644 --- a/src/mtconnect/asset/target.cpp +++ b/src/mtconnect/asset/target.cpp @@ -78,7 +78,7 @@ namespace mtconnect { entity::Requirement::Infinite), entity::Requirement("TargetRef", ValueType::ENTITY, TargetRef::getFactory(), 0, entity::Requirement::Infinite), - entity::Requirement("TargetRequirement", ValueType::ENTITY, + entity::Requirement("TargetRequirementTable", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite)}); @@ -95,7 +95,7 @@ namespace mtconnect { if (!targets) { targets = make_shared(entity::Requirements {entity::Requirement( - "TargetRequirement", ValueType::ENTITY, TargetRequirement::getFactory(), 0, + "TargetRequirementTable", ValueType::ENTITY, TargetRequirement::getFactory(), 0, entity::Requirement::Infinite)}); targets->registerMatchers(); @@ -156,7 +156,7 @@ namespace mtconnect { { factory = make_shared(*Target::getFactory()); factory->addRequirements( - {{"requirementId", true}, {"CapabilityTable", ValueType::TABLE, false}}); + {{"requirementId", true}, {"VALUE", ValueType::TABLE, true}}); } return factory; diff --git a/src/mtconnect/entity/factory.hpp b/src/mtconnect/entity/factory.hpp index 2f9474721..a62d73183 100644 --- a/src/mtconnect/entity/factory.hpp +++ b/src/mtconnect/entity/factory.hpp @@ -147,6 +147,14 @@ namespace mtconnect { /// @param name the name of the property /// @return `true` if this is a table bool isTable(const std::string &name) const { return m_tables.count(name) > 0; } + + /// @brief is the value of this entity a data set or table + /// @returns `true` if the value is a data set or table + bool isValueDataSet() const { return m_isValueDataSet; } + + /// @brief is the value of this entity a table + /// @returns `true` if the value is a table + bool isValueTable() const { return m_isValueTable; } /// @brief get the requirement pointer for a key /// @param name the property key @@ -390,9 +398,15 @@ namespace mtconnect { } else if (BaseValueType(r.getType()) == ValueType::DATA_SET) { + if (r.getName() == "VALUE") + m_isValueDataSet = true; m_dataSets.insert(r.getName()); if (r.getType() == ValueType::TABLE) + { m_tables.insert(r.getName()); + if (r.getName() == "VALUE") + m_isValueTable = true; + } } else { @@ -443,6 +457,8 @@ namespace mtconnect { size_t m_minListSize {0}; bool m_hasRaw {false}; bool m_any {false}; + bool m_isValueDataSet {false}; + bool m_isValueTable {false}; std::set m_propertySets; std::set m_dataSets; diff --git a/src/mtconnect/entity/xml_parser.cpp b/src/mtconnect/entity/xml_parser.cpp index 0a88a6269..a6e146bdd 100644 --- a/src/mtconnect/entity/xml_parser.cpp +++ b/src/mtconnect/entity/xml_parser.cpp @@ -225,6 +225,11 @@ namespace mtconnect::entity { if (holds_alternative(value)) properties.insert({"RAW", value}); } + else if (ef->isValueDataSet()) + { + auto ds = &properties["VALUE"].emplace(); + parseDataSet(node, *ds, ef->isValueTable()); + } else { int orderCount = 0; diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index d574d3bca..3554ea53f 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -316,12 +316,10 @@ TEST_F(TargetTest, verify_target_requirement) const auto doc = R"DOC( - - - ABC - 123 - - + + ABC + 123 + )DOC"; @@ -343,12 +341,10 @@ TEST_F(TargetTest, verify_target_requirement) ASSERT_EQ(1, targets->size()); auto it = targets->begin(); - ASSERT_EQ("TargetRequirement", (*it)->getName()); + ASSERT_EQ("TargetRequirementTable", (*it)->getName()); ASSERT_EQ("req1", (*it)->get("requirementId")); - ASSERT_TRUE((*it)->hasProperty("CapabilityTable")); - const auto table = (*it)->get("CapabilityTable"); - + const auto table = (*it)->getValue(); ASSERT_EQ(2, table.size()); auto rowIt = table.begin(); @@ -379,12 +375,10 @@ TEST_F(TargetTest, verify_target_requirement_in_json) const auto doc = R"DOC( - - - ABC - 123 - - + + ABC + 123 + )DOC"; @@ -407,9 +401,9 @@ TEST_F(TargetTest, verify_target_requirement_in_json) ASSERT_EQ(R"JSON({ "Root": { "Targets": { - "TargetRequirement": [ + "TargetRequirementTable": [ { - "CapabilityTable": { + "value": { "R1": { "C1": "ABC" }, diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index 21f0d3b13..a25e62d9f 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -94,15 +94,13 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) - - - - 1000 - - - 1500 - - + + + 1000 + + + 1500 + From 12cfc74dfa2a1712fe0d07941abd3c3a6320765e Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 08:37:26 +0100 Subject: [PATCH 59/84] Task archetype tests are complete --- test_package/task_test.cpp | 425 ++++++++++++++++++++++++++++++++++++- 1 file changed, 418 insertions(+), 7 deletions(-) diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index a25e62d9f..eac372fa2 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -101,7 +101,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) 1500 - + @@ -192,10 +192,10 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) ASSERT_EQ(2, targets->size()); { auto tit = targets->begin(); - EXPECT_EQ("TargetRequirement", (*tit)->getName()); + EXPECT_EQ("TargetRequirementTable", (*tit)->getName()); EXPECT_EQ("ab", (*tit)->get("requirementId")); - auto table = (*tit)->get("CapabilityTable"); + auto table = (*tit)->getValue(); ASSERT_EQ(2, table.size()); const auto &row1 = get(table.find(DataSetEntry("PAYLOAD"))->m_value); @@ -251,22 +251,433 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) TEST_F(TaskAssetTest, task_archetype_should_produce_json) { - GTEST_SKIP(); + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + + + + + + + 1000 + + + 1500 + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + entity::JsonEntityPrinter jprinter(2, true); + + auto jdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "TaskArchetype": { + "Collaborators": { + "Collaborator": [ + { + "Targets": { + "TargetRef": [ + { + "groupIdRef": "MyRobots" + } + ], + "TargetRequirementTable": [ + { + "value": { + "PAYLOAD": { + "maximum": 1000 + }, + "REACH": { + "minimum": 1500 + } + }, + "requirementId": "ab" + } + ] + }, + "collaboratorId": "Robot", + "type": "ROBOT" + }, + { + "Targets": { + "TargetDevice": [ + { + "deviceUuid": "UR890" + } + ] + }, + "collaboratorId": "robot2", + "type": "ROBOT" + } + ] + }, + "Coordinator": { + "Collaborator": { + "Targets": { + "TargetDevice": [ + { + "deviceUuid": "Mazak123" + }, + { + "deviceUuid": "Mazak456" + } + ] + }, + "collaboratorId": "machine", + "type": "CNC" + } + }, + "Priority": 10, + "SubTaskRefs": { + "SubTaskRef": [ + { + "value": "UnloadConv", + "order": 1 + }, + { + "value": "LoadCnc", + "order": 2 + } + ] + }, + "Targets": { + "TargetDevice": [ + { + "deviceUuid": "Mazak123" + }, + { + "deviceUuid": "Mazak456" + } + ], + "TargetGroup": [ + { + "TargetDevice": [ + { + "deviceUuid": "UR123" + }, + { + "deviceUuid": "UR456" + } + ], + "groupId": "MyRobots" + } + ] + }, + "TaskType": "MATERIAL_UNLOAD", + "assetId": "1aa7eece248093", + "deviceUuid": "mxi_m001", + "hash": "fCI1rCQv8BcHbzZeoMxt3kHmb9k=", + "timestamp": "2024-12-10T05:17:05.531454Z" + } +})", jdoc); } TEST_F(TaskAssetTest, task_archetype_must_have_collaborators) { - GTEST_SKIP(); + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("TaskArchetype(Collaborators): Property Collaborators is required and not provided"s, errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_archetype_must_have_collaborators_with_at_least_one_collaborator) +{ + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + EXPECT_EQ("Collaborators(Collaborator): Entity list requirement Collaborator must have at least 1 entries, 0 found"s, errors.front()->what()); } + TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator) { - GTEST_SKIP(); + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + 1000 + + + 1500 + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("TaskArchetype(Coordinator): Property Coordinator is required and not provided"s, errors.front()->what()); + +} + +TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator_with_a_collaborator) +{ + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + 1000 + + + 1500 + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + EXPECT_EQ("Coordinator(Collaborator): Property Collaborator is required and not provided"s, errors.front()->what()); } + TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_refs) { - GTEST_SKIP(); + const auto doc = + R"DOC( + MATERIAL_UNLOAD + 10 + + + + + + + + + + + + + + + + + + + + + + 1000 + + + 1500 + + + + + + + + + + + + + UnloadConv + LoadCnc + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto subtasks = asset->getList("SubTaskRefs"); + ASSERT_TRUE(subtasks); + ASSERT_EQ(2, subtasks->size()); + + { + auto it = subtasks->begin(); + EXPECT_EQ("SubTaskRef", (*it)->getName()); + EXPECT_EQ("g1", (*it)->get("group")); + EXPECT_EQ(1, (*it)->get("order")); + EXPECT_EQ(false, (*it)->get("optional")); + EXPECT_EQ(true, (*it)->get("parallel")); + EXPECT_EQ("UnloadConv", (*it)->getValue()); + + it++; + EXPECT_EQ("SubTaskRef", (*it)->getName()); + EXPECT_EQ(2, (*it)->get("order")); + EXPECT_EQ("g1", (*it)->get("group")); + EXPECT_EQ(true, (*it)->get("optional")); + EXPECT_EQ(false, (*it)->get("parallel")); + EXPECT_EQ("LoadCnc", (*it)->getValue()); + } + + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); } TEST_F(TaskAssetTest, should_parse_task) From e1143ea0d228a8d45f2438be9af77b3122cbf18f Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 10:33:10 +0100 Subject: [PATCH 60/84] Added all tests for version 2.7 --- src/mtconnect/asset/target.cpp | 3 +- src/mtconnect/asset/task.cpp | 62 ++- src/mtconnect/entity/factory.hpp | 2 +- test_package/task_test.cpp | 631 ++++++++++++++++++++++++++++--- 4 files changed, 617 insertions(+), 81 deletions(-) diff --git a/src/mtconnect/asset/target.cpp b/src/mtconnect/asset/target.cpp index e9f9ba001..9dc916ad3 100644 --- a/src/mtconnect/asset/target.cpp +++ b/src/mtconnect/asset/target.cpp @@ -155,8 +155,7 @@ namespace mtconnect { if (!factory) { factory = make_shared(*Target::getFactory()); - factory->addRequirements( - {{"requirementId", true}, {"VALUE", ValueType::TABLE, true}}); + factory->addRequirements({{"requirementId", true}, {"VALUE", ValueType::TABLE, true}}); } return factory; diff --git a/src/mtconnect/asset/task.cpp b/src/mtconnect/asset/task.cpp index 8d1461651..b85f67305 100644 --- a/src/mtconnect/asset/task.cpp +++ b/src/mtconnect/asset/task.cpp @@ -34,14 +34,12 @@ namespace mtconnect { {"type", false}, {"optional", ValueType::BOOL, false}, {"Targets", ValueType::ENTITY_LIST, Target::getAllTargetsFactory(), true}}); - - auto collaborators = make_shared( - Requirements {{"Collaborator", ValueType::ENTITY, collaborator, 1, - entity::Requirement::Infinite}}); + + auto collaborators = make_shared(Requirements { + {"Collaborator", ValueType::ENTITY, collaborator, 1, entity::Requirement::Infinite}}); auto coordinator = make_shared( - Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); - - + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); + auto ext = make_shared(); ext->registerFactory(regex(".+"), ext); ext->setAny(true); @@ -61,12 +59,11 @@ namespace mtconnect { {{"TaskType", true}, {"Priority", ValueType::INTEGER, false}, {"Coordinator", ValueType::ENTITY, coordinator, true}, - {"Collaborators", ValueType::ENTITY_LIST, collaborators, - true}, + {"Collaborators", ValueType::ENTITY_LIST, collaborators, true}, {"Targets", ValueType::ENTITY_LIST, Target::getAllTargetsFactory(), false}, {"SubTaskRefs", ValueType::ENTITY_LIST, subTaskRefs, false}}); - factory->setOrder( - {{"TaskType", "Priority", "Targets", "Coordinator", "Collaborators", "SubTaskREfs"}}); + factory->setOrder({{"Configuration", "TaskType", "Priority", "Targets", "Coordinator", + "Collaborators", "SubTaskREfs"}}); factory->registerFactory(regex(".+"), ext); factory->setAny(true); } @@ -88,18 +85,15 @@ namespace mtconnect { static FactoryPtr factory; if (!factory) { - auto collaborator = make_shared(Requirements { - {"collaboratorId", true}, - {"type", false}, - {"collaboratorDeviceUuid", false}, - {"requirementId", false}}); - - auto collaborators = make_shared( - Requirements {{"Collaborator", ValueType::ENTITY, collaborator, 1, - entity::Requirement::Infinite}}); + auto collaborator = make_shared(Requirements {{"collaboratorId", true}, + {"type", false}, + {"collaboratorDeviceUuid", false}, + {"requirementId", false}}); + + auto collaborators = make_shared(Requirements { + {"Collaborator", ValueType::ENTITY, collaborator, 1, entity::Requirement::Infinite}}); auto coordinator = make_shared( - Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); - + Requirements {{"Collaborator", ValueType::ENTITY, collaborator, true}}); auto ext = make_shared(); ext->registerFactory(regex(".+"), ext); @@ -107,23 +101,21 @@ namespace mtconnect { ext->setList(true); factory = make_shared(*Asset::getFactory()); - factory->addRequirements( - {{"TaskType", true}, - {"State", - ControlledVocab {"INACTIVE", "PREPARING", "COMMITTING", "COMMITTED", "COMPLETE", - "FAIL"}, - true}, - {"ParentTaskAssetId", false}, - {"TaskArchetypeAssetId", false}, - {"Coordinator", ValueType::ENTITY, coordinator, true}, - {"Collaborators", ValueType::ENTITY_LIST, collaborators, - true}}); + factory->addRequirements({{"TaskType", true}, + {"TaskState", + ControlledVocab {"INACTIVE", "PREPARING", "COMMITTING", + "COMMITTED", "COMPLETE", "FAIL"}, + true}, + {"ParentTaskAssetId", false}, + {"TaskArchetypeAssetId", false}, + {"Coordinator", ValueType::ENTITY, coordinator, true}, + {"Collaborators", ValueType::ENTITY_LIST, collaborators, true}}); auto task = make_shared( Requirements {{"Task", ValueType::ENTITY, factory, 1, entity::Requirement::Infinite}}); factory->addRequirements({{"SubTasks", ValueType::ENTITY_LIST, task, false}}); - factory->setOrder({"TaskType", "State", "ParentTaskAssetId", "TaskArchetypeAssetId", - "SubTasks", "Coordinator", "Collaborators"}); + factory->setOrder({"Configuration", "TaskType", "TaskState", "ParentTaskAssetId", + "TaskArchetypeAssetId", "Coordinator", "Collaborators", "SubTasks"}); factory->registerFactory(regex(".+"), ext); factory->setAny(true); } diff --git a/src/mtconnect/entity/factory.hpp b/src/mtconnect/entity/factory.hpp index a62d73183..99933eff9 100644 --- a/src/mtconnect/entity/factory.hpp +++ b/src/mtconnect/entity/factory.hpp @@ -147,7 +147,7 @@ namespace mtconnect { /// @param name the name of the property /// @return `true` if this is a table bool isTable(const std::string &name) const { return m_tables.count(name) > 0; } - + /// @brief is the value of this entity a data set or table /// @returns `true` if the value is a data set or table bool isValueDataSet() const { return m_isValueDataSet; } diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index eac372fa2..a7f60acfc 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -20,6 +20,7 @@ // Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) #include +#include #include #include #include @@ -161,7 +162,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) EXPECT_EQ("UR456", (*dit)->get("deviceUuid")); } } - + auto coordinator = asset->get("Coordinator"); ASSERT_TRUE(coordinator); EXPECT_EQ("Coordinator", coordinator->getName()); @@ -169,7 +170,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) ASSERT_TRUE(collaborator); EXPECT_EQ("machine", collaborator->get("collaboratorId")); EXPECT_EQ("CNC", collaborator->get("type")); - + auto collTargets = collaborator->getList("Targets"); ASSERT_EQ(2, collTargets->size()); { @@ -180,7 +181,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) EXPECT_EQ("TargetDevice", (*it)->getName()); EXPECT_EQ("Mazak456", (*it)->get("deviceUuid")); } - + auto collaborators = asset->getList("Collaborators"); ASSERT_EQ(2, collaborators->size()); { @@ -194,10 +195,10 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) auto tit = targets->begin(); EXPECT_EQ("TargetRequirementTable", (*tit)->getName()); EXPECT_EQ("ab", (*tit)->get("requirementId")); - + auto table = (*tit)->getValue(); ASSERT_EQ(2, table.size()); - + const auto &row1 = get(table.find(DataSetEntry("PAYLOAD"))->m_value); ASSERT_EQ(1, row1.size()); EXPECT_EQ(1000, get(row1.find(TableCell("maximum"))->m_value)); @@ -205,17 +206,17 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) const auto &row2 = get(table.find(DataSetEntry("REACH"))->m_value); ASSERT_EQ(1, row2.size()); EXPECT_EQ(1500, get(row2.find(TableCell("minimum"))->m_value)); - + tit++; ASSERT_EQ("TargetRef", (*tit)->getName()); ASSERT_EQ("MyRobots", (*tit)->get("groupIdRef")); } - + it++; auto collab2 = *it; EXPECT_EQ("robot2", collab2->get("collaboratorId")); EXPECT_EQ("ROBOT", collab2->get("type")); - + auto targets2 = collab2->getList("Targets"); ASSERT_EQ(1, targets2->size()); { @@ -228,7 +229,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) auto subtasks = asset->getList("SubTaskRefs"); ASSERT_TRUE(subtasks); ASSERT_EQ(2, subtasks->size()); - + { auto it = subtasks->begin(); EXPECT_EQ("SubTaskRef", (*it)->getName()); @@ -240,7 +241,7 @@ TEST_F(TaskAssetTest, should_parse_a_part_archetype) EXPECT_EQ(2, (*it)->get("order")); EXPECT_EQ("LoadCnc", (*it)->getValue()); } - + // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); @@ -297,15 +298,15 @@ TEST_F(TaskAssetTest, task_archetype_should_produce_json) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + entity::JsonEntityPrinter jprinter(2, true); - + auto jdoc = jprinter.print(entity); EXPECT_EQ(R"({ "TaskArchetype": { @@ -406,7 +407,8 @@ TEST_F(TaskAssetTest, task_archetype_should_produce_json) "hash": "fCI1rCQv8BcHbzZeoMxt3kHmb9k=", "timestamp": "2024-12-10T05:17:05.531454Z" } -})", jdoc); +})", + jdoc); } TEST_F(TaskAssetTest, task_archetype_must_have_collaborators) @@ -437,14 +439,15 @@ TEST_F(TaskAssetTest, task_archetype_must_have_collaborators) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(1, errors.size()); - - EXPECT_EQ("TaskArchetype(Collaborators): Property Collaborators is required and not provided"s, errors.front()->what()); + + EXPECT_EQ("TaskArchetype(Collaborators): Property Collaborators is required and not provided"s, + errors.front()->what()); } TEST_F(TaskAssetTest, task_archetype_must_have_collaborators_with_at_least_one_collaborator) @@ -476,16 +479,17 @@ TEST_F(TaskAssetTest, task_archetype_must_have_collaborators_with_at_least_one_c )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(3, errors.size()); - - EXPECT_EQ("Collaborators(Collaborator): Entity list requirement Collaborator must have at least 1 entries, 0 found"s, errors.front()->what()); -} + EXPECT_EQ( + "Collaborators(Collaborator): Entity list requirement Collaborator must have at least 1 entries, 0 found"s, + errors.front()->what()); +} TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator) { @@ -527,15 +531,15 @@ TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator) )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(1, errors.size()); - - EXPECT_EQ("TaskArchetype(Coordinator): Property Coordinator is required and not provided"s, errors.front()->what()); + EXPECT_EQ("TaskArchetype(Coordinator): Property Coordinator is required and not provided"s, + errors.front()->what()); } TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator_with_a_collaborator) @@ -580,16 +584,16 @@ TEST_F(TaskAssetTest, task_archetype_must_have_a_coordinator_with_a_collaborator )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(3, errors.size()); - - EXPECT_EQ("Coordinator(Collaborator): Property Collaborator is required and not provided"s, errors.front()->what()); -} + EXPECT_EQ("Coordinator(Collaborator): Property Collaborator is required and not provided"s, + errors.front()->what()); +} TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_refs) { @@ -639,20 +643,20 @@ TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_re )DOC"; - + ErrorList errors; entity::XmlParser parser; - + auto entity = parser.parse(Asset::getRoot(), doc, errors); ASSERT_EQ(0, errors.size()); - + auto asset = dynamic_cast(entity.get()); ASSERT_NE(nullptr, asset); auto subtasks = asset->getList("SubTaskRefs"); ASSERT_TRUE(subtasks); ASSERT_EQ(2, subtasks->size()); - + { auto it = subtasks->begin(); EXPECT_EQ("SubTaskRef", (*it)->getName()); @@ -661,7 +665,7 @@ TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_re EXPECT_EQ(false, (*it)->get("optional")); EXPECT_EQ(true, (*it)->get("parallel")); EXPECT_EQ("UnloadConv", (*it)->getValue()); - + it++; EXPECT_EQ("SubTaskRef", (*it)->getName()); EXPECT_EQ(2, (*it)->get("order")); @@ -670,24 +674,565 @@ TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_re EXPECT_EQ(false, (*it)->get("parallel")); EXPECT_EQ("LoadCnc", (*it)->getValue()); } - // Round trip test entity::XmlPrinter printer; printer.print(*m_writer, entity, {}); - + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); +} + +TEST_F(TaskAssetTest, should_parse_simple_task) +{ + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + EXPECT_EQ("Task", asset->getName()); + EXPECT_EQ("2aa7eece24", asset->getAssetId()); + EXPECT_EQ("mxi_m001", asset->get("deviceUuid")); + + auto configuration = asset->get("Configuration"); + ASSERT_TRUE(configuration); + + auto relationships = configuration->getList("Relationships"); + ASSERT_TRUE(relationships); + ASSERT_EQ(1, relationships->size()); + + { + auto it = relationships->begin(); + ASSERT_EQ("A", (*it)->get("id")); + ASSERT_EQ("1aa7eece248093", (*it)->get("assetIdRef")); + ASSERT_EQ("PEER", (*it)->get("type")); + ASSERT_EQ("TASK_ARCHETYPE", (*it)->get("assetType")); + } + + auto coordinator = asset->get("Coordinator"); + ASSERT_TRUE(coordinator); + + auto collab = coordinator->get("Collaborator"); + ASSERT_TRUE(collab); + EXPECT_EQ("machine", collab->get("collaboratorId")); + EXPECT_EQ("xyz", collab->get("collaboratorDeviceUuid")); + + auto collaborators = asset->getList("Collaborators"); + ASSERT_TRUE(collaborators); + + { + auto it = collaborators->begin(); + auto collab1 = *it; + EXPECT_EQ("robot1", collab1->get("collaboratorId")); + EXPECT_EQ("abc", collab1->get("collaboratorDeviceUuid")); + EXPECT_EQ("ab", collab1->get("requirementId")); + + it++; + auto collab2 = *it; + EXPECT_EQ("robot2", collab2->get("collaboratorId")); + EXPECT_EQ("Mazak123", collab2->get("collaboratorDeviceUuid")); + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + string content = m_writer->getContent(); ASSERT_EQ(content, doc); } -TEST_F(TaskAssetTest, should_parse_task) +TEST_F(TaskAssetTest, should_parse_simple_task_with_subtasks) { - GTEST_SKIP(); + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + + + + + + + OPEN_DOOR + COMMITTED + 2aa7eece24 + + + + + + + + + + OPEN_CHUCK + COMMITTED + 2aa7eece24 + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + auto asset = dynamic_cast(entity.get()); + ASSERT_NE(nullptr, asset); + + auto subtasks = asset->getList("SubTasks"); + ASSERT_TRUE(subtasks); + ASSERT_EQ(2, subtasks->size()); + + auto it = subtasks->begin(); + { + auto task = *it; + EXPECT_EQ("Task", task->getName()); + EXPECT_EQ("4afb7fc0", task->get("assetId")); + EXPECT_EQ("mxi_m001", task->get("deviceUuid")); + EXPECT_EQ("OPEN_DOOR", task->get("TaskType")); + EXPECT_EQ("COMMITTED", task->get("TaskState")); + EXPECT_EQ("2aa7eece24", task->get("ParentTaskAssetId")); + + auto coordinator = task->get("Coordinator"); + ASSERT_TRUE(coordinator); + + auto collab = coordinator->get("Collaborator"); + ASSERT_TRUE(collab); + + EXPECT_EQ("robot1", collab->get("collaboratorId")); + EXPECT_EQ("UR012", collab->get("collaboratorDeviceUuid")); + + auto collaborators = task->getList("Collaborators"); + EXPECT_TRUE(collaborators); + EXPECT_EQ(2, collaborators->size()); + { + auto cit = collaborators->begin(); + EXPECT_EQ("machine", (*cit)->get("collaboratorId")); + EXPECT_EQ("CNC", (*cit)->get("collaboratorDeviceUuid")); + EXPECT_EQ("ab", (*cit)->get("requirementId")); + + cit++; + EXPECT_EQ("robot2", (*cit)->get("collaboratorId")); + EXPECT_EQ("UR543", (*cit)->get("collaboratorDeviceUuid")); + } + } + + it++; + { + auto task = *it; + EXPECT_EQ("Task", task->getName()); + EXPECT_EQ("a9ef8c40", task->get("assetId")); + EXPECT_EQ("mxi_m001", task->get("deviceUuid")); + EXPECT_EQ("OPEN_CHUCK", task->get("TaskType")); + EXPECT_EQ("COMMITTED", task->get("TaskState")); + EXPECT_EQ("2aa7eece24", task->get("ParentTaskAssetId")); + + auto coordinator = task->get("Coordinator"); + ASSERT_TRUE(coordinator); + + auto collab = coordinator->get("Collaborator"); + ASSERT_TRUE(collab); + + EXPECT_EQ("machine", collab->get("collaboratorId")); + EXPECT_EQ("CNC", collab->get("collaboratorDeviceUuid")); + + auto collaborators = task->getList("Collaborators"); + EXPECT_TRUE(collaborators); + EXPECT_EQ(1, collaborators->size()); + { + auto cit = collaborators->begin(); + EXPECT_EQ("robot2", (*cit)->get("collaboratorId")); + EXPECT_EQ("UR543", (*cit)->get("collaboratorDeviceUuid")); + EXPECT_EQ("ab", (*cit)->get("requirementId")); + } + } + + // Round trip test + entity::XmlPrinter printer; + printer.print(*m_writer, entity, {}); + + string content = m_writer->getContent(); + ASSERT_EQ(content, doc); } TEST_F(TaskAssetTest, task_should_produce_json) { - GTEST_SKIP(); + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + + + + + + + OPEN_DOOR + COMMITTED + 2aa7eece24 + + + + + + + + + + OPEN_CHUCK + COMMITTED + 2aa7eece24 + + + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(0, errors.size()); + + entity::JsonEntityPrinter jprinter(2, true); + + auto jdoc = jprinter.print(entity); + EXPECT_EQ(R"({ + "Task": { + "Collaborators": { + "Collaborator": [ + { + "collaboratorDeviceUuid": "UR543", + "collaboratorId": "robot1", + "requirementId": "ab" + }, + { + "collaboratorDeviceUuid": "UR012", + "collaboratorId": "robot2" + } + ] + }, + "Configuration": { + "Relationships": { + "AssetRelationship": [ + { + "assetIdRef": "1aa7eece248093", + "assetType": "TASK_ARCHETYPE", + "id": "A", + "type": "PEER" + } + ] + } + }, + "Coordinator": { + "Collaborator": { + "collaboratorDeviceUuid": "CNC", + "collaboratorId": "machine" + } + }, + "ParentTaskAssetId": "dfgfdghfkj", + "SubTasks": { + "Task": [ + { + "Collaborators": { + "Collaborator": [ + { + "collaboratorDeviceUuid": "CNC", + "collaboratorId": "machine", + "requirementId": "ab" + }, + { + "collaboratorDeviceUuid": "UR543", + "collaboratorId": "robot2" + } + ] + }, + "Coordinator": { + "Collaborator": { + "collaboratorDeviceUuid": "UR012", + "collaboratorId": "robot1" + } + }, + "ParentTaskAssetId": "2aa7eece24", + "TaskState": "COMMITTED", + "TaskType": "OPEN_DOOR", + "assetId": "4afb7fc0", + "deviceUuid": "mxi_m001", + "timestamp": "2024-12-10T05:17:05.531454Z" + }, + { + "Collaborators": { + "Collaborator": [ + { + "collaboratorDeviceUuid": "UR543", + "collaboratorId": "robot2", + "requirementId": "ab" + } + ] + }, + "Coordinator": { + "Collaborator": { + "collaboratorDeviceUuid": "CNC", + "collaboratorId": "machine" + } + }, + "ParentTaskAssetId": "2aa7eece24", + "TaskState": "COMMITTED", + "TaskType": "OPEN_CHUCK", + "assetId": "a9ef8c40", + "deviceUuid": "mxi_m001", + "timestamp": "2024-12-10T05:17:05.531454Z" + } + ] + }, + "TaskState": "COMMITTED", + "TaskType": "MATERIAL_UNLOAD", + "assetId": "2aa7eece24", + "deviceUuid": "mxi_m001", + "hash": "fCI1rCQv8BcHbzZeoMxt3kHmb9k=", + "timestamp": "2024-12-10T05:17:05.531454Z" + } +})", + jdoc); } +TEST_F(TaskAssetTest, task_must_have_a_coordinator) +{ + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("Task(Coordinator): Property Coordinator is required and not provided"s, + errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_must_have_a_coordinator_with_a_collaborator) +{ + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + EXPECT_EQ("Coordinator(Collaborator): Property Collaborator is required and not provided"s, + errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_must_have_collaborators) +{ + const auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + COMMITTED + dfgfdghfkj + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("Task(Collaborators): Property Collaborators is required and not provided"s, + errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_must_have_collaborators_with_at_least_one_collaborator) +{ + const auto doc = + R"DOC( + + + + + +MATERIAL_UNLOAD +COMMITTED +dfgfdghfkj + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(3, errors.size()); + + EXPECT_EQ( + "Collaborators(Collaborator): Entity list requirement Collaborator must have at least 1 entries, 0 found"s, + errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_should_accept_all_task_states) +{ + constexpr auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + {} + dfgfdghfkj + + + + + + + +)DOC"; + + list states {"INACTIVE", "PREPARING", "COMMITTING", "COMMITTED", "COMPLETE", "FAIL"}; + + for (const auto &state : states) + { + ErrorList errors; + entity::XmlParser parser; + + auto sd = format(doc, state); + auto entity = parser.parse(Asset::getRoot(), sd, errors); + EXPECT_EQ(0, errors.size()) << "Invalid task state: " << state; + } +} + +TEST_F(TaskAssetTest, task_should_not_accept_invalid_task_states) +{ + constexpr auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + {} + dfgfdghfkj + + + + + + + +)DOC"; + + list states {"BAD", "STATE", "123", "DONE", ""}; + + for (const auto &state : states) + { + ErrorList errors; + entity::XmlParser parser; + + auto sd = format(doc, state); + auto entity = parser.parse(Asset::getRoot(), sd, errors); + EXPECT_EQ(1, errors.size()) << "Should fail: " << state; + } +} From 1f5b3a898c5864e4bdd0befbc27dde5cef25c16b Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 10:34:56 +0100 Subject: [PATCH 61/84] minor change --- test_package/task_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index a7f60acfc..1d7283f8e 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -1199,7 +1199,7 @@ TEST_F(TaskAssetTest, task_should_accept_all_task_states) auto sd = format(doc, state); auto entity = parser.parse(Asset::getRoot(), sd, errors); - EXPECT_EQ(0, errors.size()) << "Invalid task state: " << state; + EXPECT_EQ(0, errors.size()) << "should accept task state: " << state; } } @@ -1233,6 +1233,6 @@ TEST_F(TaskAssetTest, task_should_not_accept_invalid_task_states) auto sd = format(doc, state); auto entity = parser.parse(Asset::getRoot(), sd, errors); - EXPECT_EQ(1, errors.size()) << "Should fail: " << state; + EXPECT_EQ(1, errors.size()) << "Should fail task state: " << state; } } From d931e3260218c87e499a1c4ddb7c6e8545be4c4d Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 10:57:34 +0100 Subject: [PATCH 62/84] Fixed tests that used to use fictitious Part asset. Part is now defined. Changed to FakeAsset --- test_package/agent_asset_test.cpp | 244 +++++++++++++++--------------- test_package/asset_hash_test.cpp | 54 +++---- test_package/task_test.cpp | 60 ++++++++ 3 files changed, 209 insertions(+), 149 deletions(-) diff --git a/test_package/agent_asset_test.cpp b/test_package/agent_asset_test.cpp index bb934e015..36b8d9843 100644 --- a/test_package/agent_asset_test.cpp +++ b/test_package/agent_asset_test.cpp @@ -95,10 +95,10 @@ TEST_F(AgentAssetTest, should_store_assets_in_buffer) auto rest = m_agentTestHelper->getRestService(); ASSERT_TRUE(rest->getServer()->arePutsAllowed()); - string body = "TEST"; + string body = "TEST"; QueryMap queries; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; queries["device"] = "LinuxCNC"; ASSERT_EQ((unsigned int)4, agent->getAssetStorage()->getMaxAssets()); @@ -113,14 +113,14 @@ TEST_F(AgentAssetTest, should_store_assets_in_buffer) PARSE_XML_RESPONSE("/asset/123"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetBufferSize", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST"); } // The device should generate an asset changed event as well. { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged", "123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged@assetType", "FakeAsset"); } } @@ -130,10 +130,10 @@ TEST_F(AgentAssetTest, should_store_assets_in_buffer_and_generate_asset_added) auto rest = m_agentTestHelper->getRestService(); ASSERT_TRUE(rest->getServer()->arePutsAllowed()); - string body = "TEST"; + string body = "TEST"; QueryMap queries; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; queries["device"] = "LinuxCNC"; ASSERT_EQ((unsigned int)4, agent->getAssetStorage()->getMaxAssets()); @@ -148,25 +148,25 @@ TEST_F(AgentAssetTest, should_store_assets_in_buffer_and_generate_asset_added) PARSE_XML_RESPONSE("/asset/123"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetBufferSize", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST"); } // The device should generate an asset added event as well. { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetAdded", "123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetAdded@assetType", "FakeAsset"); } } TEST_F(AgentAssetTest, should_handle_asset_buffer_and_buffer_limits) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - string body = "TEST 1"; + string body = "TEST 1"; QueryMap queries; queries["device"] = "000"; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; const auto &storage = agent->getAssetStorage(); @@ -176,51 +176,51 @@ TEST_F(AgentAssetTest, should_handle_asset_buffer_and_buffer_limits) { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); + ASSERT_EQ(1, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 1"); } // Make sure replace works properly { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); + ASSERT_EQ(1, storage->getCountForType("FakeAsset")); } - body = "TEST 2"; + body = "TEST 2"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)2, storage->getCount()); - ASSERT_EQ(2, storage->getCountForType("Part")); + ASSERT_EQ(2, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P2"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 2"); } - body = "TEST 3"; + body = "TEST 3"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)3, storage->getCount()); - ASSERT_EQ(3, storage->getCountForType("Part")); + ASSERT_EQ(3, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P3"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 3"); } - body = "TEST 4"; + body = "TEST 4"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); @@ -230,52 +230,52 @@ TEST_F(AgentAssetTest, should_handle_asset_buffer_and_buffer_limits) { PARSE_XML_RESPONSE("/asset/P4"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 4"); - ASSERT_EQ(4, storage->getCountForType("Part")); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 4"); + ASSERT_EQ(4, storage->getCountForType("FakeAsset")); } // Test multiple asset get { PARSE_XML_RESPONSE("/assets"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[4]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[3]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[2]", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[1]", "TEST 4"); } // Test multiple asset get with filter { PARSE_XML_RESPONSE_QUERY("/asset", queries); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[4]", "TEST 4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[3]", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[1]", "TEST 1"); } queries["count"] = "2"; { PARSE_XML_RESPONSE_QUERY("/assets", queries); ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[1]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset[2]", "TEST 2"); } queries.erase("count"); - body = "TEST 5"; + body = "TEST 5"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); + ASSERT_EQ(4, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P5"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 5"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 5"); } { @@ -284,46 +284,46 @@ TEST_F(AgentAssetTest, should_handle_asset_buffer_and_buffer_limits) ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: P1"); } - body = "TEST 6"; + body = "TEST 6"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); + ASSERT_EQ(4, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P3"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 6"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 6"); } { PARSE_XML_RESPONSE("/asset/P2"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 2"); } - body = "TEST 7"; + body = "TEST 7"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); + ASSERT_EQ(4, storage->getCountForType("FakeAsset")); } - body = "TEST 8"; + body = "TEST 8"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); + ASSERT_EQ(4, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P6"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 8"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 8"); } // Now since two and three have been modified, asset 4 should be removed. @@ -383,14 +383,14 @@ TEST_F(AgentAssetTest, should_handle_asset_from_adapter_on_one_line) const auto &storage = agent->getAssetStorage(); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 1"); } } @@ -401,14 +401,14 @@ TEST_F(AgentAssetTest, should_handle_multiline_asset) const auto &storage = agent->getAssetStorage(); m_agentTestHelper->m_adapter->parseBuffer( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA\n"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|--multiline--AAAA\n"); m_agentTestHelper->m_adapter->parseBuffer( - "\n" + "\n" " TEST 1\n" " Some Text\n" " XXX\n"); m_agentTestHelper->m_adapter->parseBuffer( - "\n" + "\n" "--multiline--AAAA\n"); ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); ASSERT_EQ((unsigned int)1, storage->getCount()); @@ -416,11 +416,11 @@ TEST_F(AgentAssetTest, should_handle_multiline_asset) { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:PartXXX", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:Extra", "XXX"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@deviceUuid", "000"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2021-02-01T12:00:00Z"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:PartXXX", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:Extra", "XXX"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@assetId", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@deviceUuid", "000"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@timestamp", "2021-02-01T12:00:00Z"); } // Make sure we can still add a line and we are out of multiline mode... @@ -446,11 +446,11 @@ TEST_F(AgentAssetTest, should_handle_bad_asset_from_adapter) TEST_F(AgentAssetTest, should_handle_asset_removal_from_REST_api) { - string body = "TEST 1"; + string body = "TEST 1"; QueryMap query; query["device"] = "LinuxCNC"; - query["type"] = "Part"; + query["type"] = "FakeAsset"; const auto &storage = m_agentTestHelper->m_agent->getAssetStorage(); @@ -460,62 +460,62 @@ TEST_F(AgentAssetTest, should_handle_asset_removal_from_REST_api) { PARSE_XML_RESPONSE_PUT("/asset", body, query); ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); + ASSERT_EQ(1, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 1"); } // Make sure replace works properly { PARSE_XML_RESPONSE_PUT("/asset", body, query); ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); + ASSERT_EQ(1, storage->getCountForType("FakeAsset")); } - body = "TEST 2"; + body = "TEST 2"; { PARSE_XML_RESPONSE_PUT("/asset", body, query); ASSERT_EQ((unsigned int)2, storage->getCount()); - ASSERT_EQ(2, storage->getCountForType("Part")); + ASSERT_EQ(2, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P2"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 2"); } - body = "TEST 3"; + body = "TEST 3"; { PARSE_XML_RESPONSE_PUT("/asset", body, query); ASSERT_EQ((unsigned int)3, storage->getCount()); - ASSERT_EQ(3, storage->getCountForType("Part")); + ASSERT_EQ(3, storage->getCountForType("FakeAsset")); } { PARSE_XML_RESPONSE("/asset/P3"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 3"); } - body = "TEST 2"; + body = "TEST 2"; { PARSE_XML_RESPONSE_PUT("/asset", body, query); ASSERT_EQ((unsigned int)3, storage->getCount(false)); - ASSERT_EQ(3, storage->getCountForType("Part", false)); + ASSERT_EQ(3, storage->getCountForType("FakeAsset", false)); } { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); } { @@ -548,21 +548,21 @@ TEST_F(AgentAssetTest, should_handle_asset_removal_from_adapter) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); + "2021-02-01T12:00:00Z|@ASSET@|P2|FakeAsset|TEST 2"); ASSERT_EQ((unsigned int)2, storage->getCount()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); + "2021-02-01T12:00:00Z|@ASSET@|P3|FakeAsset|TEST 3"); ASSERT_EQ((unsigned int)3, storage->getCount()); { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P2\r"); @@ -571,7 +571,7 @@ TEST_F(AgentAssetTest, should_handle_asset_removal_from_adapter) { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); } { @@ -650,15 +650,15 @@ TEST_F(AgentAssetTest, AssetPrependId) const auto &storage = agent->getAssetStorage(); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|@1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|@1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/asset/0001"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "0001"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@assetId", "0001"); } } @@ -671,13 +671,13 @@ TEST_F(AgentAssetTest, should_remove_changed_asset) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); @@ -686,9 +686,9 @@ TEST_F(AgentAssetTest, should_remove_changed_asset) { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } } @@ -703,26 +703,26 @@ TEST_F(AgentAssetTest, should_remove_changed_observation_asset_in_2_6) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); } m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 2"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 2"); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "FakeAsset"); } } @@ -737,13 +737,13 @@ TEST_F(AgentAssetTest, should_remove_added_asset_observation_in_2_6) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount()); { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "FakeAsset"); } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); @@ -752,9 +752,9 @@ TEST_F(AgentAssetTest, should_remove_added_asset_observation_in_2_6) { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "FakeAsset"); } } @@ -767,13 +767,13 @@ TEST_F(AgentAssetTest, should_remove_asset_using_http_delete) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount(false)); { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } { @@ -783,7 +783,7 @@ TEST_F(AgentAssetTest, should_remove_asset_using_http_delete) { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); } } @@ -820,32 +820,32 @@ TEST_F(AgentAssetTest, should_remove_all_assets) ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); ASSERT_EQ((unsigned int)1, storage->getCount()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); + "2021-02-01T12:00:00Z|@ASSET@|P2|FakeAsset|TEST 2"); ASSERT_EQ((unsigned int)2, storage->getCount()); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); + "2021-02-01T12:00:00Z|@ASSET@|P3|FakeAsset|TEST 3"); ASSERT_EQ((unsigned int)3, storage->getCount()); { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|Part"); + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|FakeAsset"); ASSERT_EQ((unsigned int)3, storage->getCount(false)); { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "FakeAsset"); } ASSERT_EQ((unsigned int)0, storage->getCount()); @@ -871,12 +871,12 @@ TEST_F(AgentAssetTest, should_remove_all_assets) TEST_F(AgentAssetTest, probe_should_have_the_asset_counts) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - string body = "TEST 1"; + string body = "TEST 1"; QueryMap queries; const auto &storage = agent->getAssetStorage(); queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); @@ -889,7 +889,7 @@ TEST_F(AgentAssetTest, probe_should_have_the_asset_counts) { PARSE_XML_RESPONSE("/probe"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount@assetType", "FakeAsset"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount", "2"); } } @@ -963,21 +963,21 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) addAdapter(); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|TEST 1"); { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "FakeAsset"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); } m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 1"); + "2021-02-01T12:00:00Z|@ASSET@|P2|FakeAsset|TEST 1"); { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "FakeAsset"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "2"); } m_agentTestHelper->m_adapter->processData( @@ -985,7 +985,7 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "2"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "1"); } @@ -994,7 +994,7 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "2"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "2"); } @@ -1003,7 +1003,7 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "2"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); } @@ -1011,7 +1011,7 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); } @@ -1019,7 +1019,7 @@ TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", 0); + ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", 0); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); } @@ -1035,12 +1035,12 @@ TEST_F(AgentAssetTest, asset_count_should_not_occur_in_header_post_20) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - string body = "TEST 1"; + string body = "TEST 1"; QueryMap queries; const auto &storage = agent->getAssetStorage(); queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; { PARSE_XML_RESPONSE_PUT("/asset", body, queries); @@ -1061,12 +1061,12 @@ TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - string body1 = "TEST 1"; + string body1 = "TEST 1"; QueryMap queries; const auto &storage = agent->getAssetStorage(); queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; + queries["type"] = "FakeAsset"; { PARSE_XML_RESPONSE_PUT("/asset", body1, queries); @@ -1075,7 +1075,7 @@ TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); } string body2 = "TEST 2"; @@ -1088,7 +1088,7 @@ TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); } @@ -1098,7 +1098,7 @@ TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) } { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "2"); } @@ -1110,7 +1110,7 @@ TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) } { PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='FakeAsset']", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); } } @@ -1119,7 +1119,7 @@ TEST_F(AgentAssetTest, asset_should_also_work_using_post_with_assets) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - string body = "TEST 1"; + string body = "TEST 1"; QueryMap queries; const auto &storage = agent->getAssetStorage(); diff --git a/test_package/asset_hash_test.cpp b/test_package/asset_hash_test.cpp index 4705947ad..d33cf314f 100644 --- a/test_package/asset_hash_test.cpp +++ b/test_package/asset_hash_test.cpp @@ -76,12 +76,12 @@ TEST_F(AssetHashTest, should_assign_hash_when_receiving_asset) const auto &storage = agent->getAssetStorage(); m_agentTestHelper->m_adapter->parseBuffer( - R"("2021-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA - + R"("2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|--multiline--AAAA + TEST 1 Some Text XXX - + --multiline--AAAA )"); ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); @@ -93,12 +93,12 @@ TEST_F(AssetHashTest, should_assign_hash_when_receiving_asset) { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:PartXXX", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:Extra", "XXX"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@deviceUuid", "000"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2021-02-01T12:00:00Z"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@hash", hash.c_str()); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:PartXXX", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:Extra", "XXX"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@assetId", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@deviceUuid", "000"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@timestamp", "2021-02-01T12:00:00Z"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@hash", hash.c_str()); } { @@ -107,12 +107,12 @@ TEST_F(AssetHashTest, should_assign_hash_when_receiving_asset) } m_agentTestHelper->m_adapter->parseBuffer( - R"("2023-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA - + R"("2023-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|--multiline--AAAA + TEST 1 Some Text XXX - + --multiline--AAAA )"); @@ -123,8 +123,8 @@ TEST_F(AssetHashTest, should_assign_hash_when_receiving_asset) { PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2023-02-01T12:00:00Z"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@hash", hash.c_str()); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@timestamp", "2023-02-01T12:00:00Z"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@hash", hash.c_str()); } { @@ -140,12 +140,12 @@ TEST_F(AssetHashTest, hash_should_change_when_doc_changes) const auto &storage = agent->getAssetStorage(); m_agentTestHelper->m_adapter->parseBuffer( - R"("2021-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA - + R"("2021-02-01T12:00:00Z|@ASSET@|P1|FakeAsset|--multiline--AAAA + TEST 1 Some Text XXX - + --multiline--AAAA )"); ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); @@ -157,12 +157,12 @@ TEST_F(AssetHashTest, hash_should_change_when_doc_changes) { PARSE_XML_RESPONSE("/asset/P1"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:PartXXX", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:Extra", "XXX"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@deviceUuid", "000"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2021-02-01T12:00:00Z"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@hash", hash.c_str()); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:PartXXX", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset/m:Extra", "XXX"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@assetId", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@deviceUuid", "000"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@timestamp", "2021-02-01T12:00:00Z"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@hash", hash.c_str()); } { @@ -172,11 +172,11 @@ TEST_F(AssetHashTest, hash_should_change_when_doc_changes) m_agentTestHelper->m_adapter->parseBuffer( R"("2023-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA - + TEST 2 Some Text XXX - + --multiline--AAAA )"); @@ -187,8 +187,8 @@ TEST_F(AssetHashTest, hash_should_change_when_doc_changes) { PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2023-02-01T12:00:00Z"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@hash", hash2.c_str()); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@timestamp", "2023-02-01T12:00:00Z"); + ASSERT_XML_PATH_EQUAL(doc, "//m:FakeAsset@hash", hash2.c_str()); } { diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index 1d7283f8e..a78ac56c4 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -1169,6 +1169,66 @@ TEST_F(TaskAssetTest, task_must_have_collaborators_with_at_least_one_collaborato errors.front()->what()); } +TEST_F(TaskAssetTest, task_must_have_a_task_state) +{ + constexpr auto doc = + R"DOC( + + + + + + MATERIAL_UNLOAD + dfgfdghfkj + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("Task(TaskState): Property TaskState is required and not provided"s, + errors.front()->what()); +} + +TEST_F(TaskAssetTest, task_must_have_a_task_type) +{ + constexpr auto doc = + R"DOC( + + + + + + COMMITTED + dfgfdghfkj + + + + + + + +)DOC"; + + ErrorList errors; + entity::XmlParser parser; + + auto entity = parser.parse(Asset::getRoot(), doc, errors); + ASSERT_EQ(1, errors.size()); + + EXPECT_EQ("Task(TaskType): Property TaskType is required and not provided"s, + errors.front()->what()); +} + TEST_F(TaskAssetTest, task_should_accept_all_task_states) { constexpr auto doc = From 9ef9d237f8605c81e3c2bd0a4045362e4dcc18f7 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 4 Dec 2025 11:14:51 +0100 Subject: [PATCH 63/84] Fixed copilot issues --- test_package/part_test.cpp | 2 +- test_package/process_test.cpp | 4 ++-- test_package/target_test.cpp | 4 ++-- test_package/task_test.cpp | 6 ++++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test_package/part_test.cpp b/test_package/part_test.cpp index a7c44f791..8cff804fd 100644 --- a/test_package/part_test.cpp +++ b/test_package/part_test.cpp @@ -33,7 +33,7 @@ #include "mtconnect/asset/part.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/entity/xml_printer.hpp" -#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/printer/xml_printer_helper.hpp" #include "mtconnect/source/adapter/adapter.hpp" using json = nlohmann::json; diff --git a/test_package/process_test.cpp b/test_package/process_test.cpp index ca659ad88..35b391f19 100644 --- a/test_package/process_test.cpp +++ b/test_package/process_test.cpp @@ -33,7 +33,7 @@ #include "mtconnect/asset/process.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/entity/xml_printer.hpp" -#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/printer/xml_printer_helper.hpp" #include "mtconnect/source/adapter/adapter.hpp" using json = nlohmann::json; @@ -454,7 +454,7 @@ TEST_F(ProcessAssetTest, process_archetype_routing_must_have_a_process_step) } } -TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precidence_and_be_options) +TEST_F(ProcessAssetTest, activity_can_have_a_sequence_precedence_and_be_optional) { const auto doc = R"DOC( diff --git a/test_package/target_test.cpp b/test_package/target_test.cpp index 3554ea53f..f6e7af2b1 100644 --- a/test_package/target_test.cpp +++ b/test_package/target_test.cpp @@ -35,8 +35,8 @@ #include "mtconnect/entity/json_printer.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/entity/xml_printer.hpp" -#include "mtconnect/printer//xml_printer.hpp" -#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/printer/xml_printer.hpp" +#include "mtconnect/printer/xml_printer_helper.hpp" #include "mtconnect/source/adapter/adapter.hpp" using json = nlohmann::json; diff --git a/test_package/task_test.cpp b/test_package/task_test.cpp index a78ac56c4..cf72162db 100644 --- a/test_package/task_test.cpp +++ b/test_package/task_test.cpp @@ -34,7 +34,7 @@ #include "mtconnect/asset/task.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/entity/xml_printer.hpp" -#include "mtconnect/printer//xml_printer_helper.hpp" +#include "mtconnect/printer/xml_printer_helper.hpp" #include "mtconnect/source/adapter/adapter.hpp" using json = nlohmann::json; @@ -68,7 +68,7 @@ class TaskAssetTest : public testing::Test std::unique_ptr m_agentTestHelper; }; -/// @section PartArchetype tests +/// @section TaskArchetype tests TEST_F(TaskAssetTest, should_parse_a_part_archetype) { @@ -683,6 +683,8 @@ TEST_F(TaskAssetTest, task_archetype_should_have_optional_fields_for_sub_task_re ASSERT_EQ(content, doc); } +/// @section Task tests + TEST_F(TaskAssetTest, should_parse_simple_task) { const auto doc = From f32fe1a98e121511ce865a0179b8a01275a10738 Mon Sep 17 00:00:00 2001 From: Ayush Thakur Date: Thu, 4 Dec 2025 20:01:31 +0530 Subject: [PATCH 64/84] Updated the structure and content of readme --- README.md | 2074 ++++++++--------------------------------------------- 1 file changed, 314 insertions(+), 1760 deletions(-) diff --git a/README.md b/README.md index e1647b101..7e9b47100 100755 --- a/README.md +++ b/README.md @@ -1,1874 +1,428 @@ +# 🚀 MTConnect C++ Agent +A modern and extensible C++ implementation of the MTConnect standard, designed to reliably collect machine data from diverse industrial devices and expose it in a consistent, structured format. The agent normalizes raw device signals, ensures compliance with the MTConnect standard, and makes the data accessible to client applications such as MES systems, monitoring dashboards, analytics platforms, or custom integrations. -MTConnect C++ Agent Version 2.5 --------- -[![Build MTConnect C++ Agent](https://github.com/mtconnect/cppagent/actions/workflows/build.yml/badge.svg)](https://github.com/mtconnect/cppagent/actions/workflows/build.yml) +The agent supports multiple industrial communication models (SHDR, JSON, MQTT), multiple devices, secure network transport, and pluggable data transformation logic, making it suitable for both small installations and large-scale production environments. -The C++ Agent provides the a complete implementation of the HTTP -server required by the MTConnect standard. The agent provides the -protocol and data collection framework that will work as a standalone -server. Once built, you only need to specify the XML description of -the devices and the location of the adapter. +--- -**NOTE: This version cannot currently be built on Windows XP since there is currently no support for the XP toolchain and C++ 17.** +# 📑 Table of Contents -Pre-built binary releases for Windows are available from [Releases](https://github.com/mtconnect/cppagent/releases) for those who do not want to build the agent themselves. For *NIX users, you will need libxml2, cppunit, and cmake as well as build essentials. +- [Overview](#overview) +- [Key Features](#key-features) +- [Architecture](#architecture) +- [Quick Start](#quick-start) +- [Installation](#installation) + - [Linux Build](#linux-build) + - [Windows Build](#windows-build) + - [macOS Build](#macos-build) +- [Basic Configuration](#basic-configuration) +- [Advanced Configuration](#advanced-configuration) + - [Multiple Adapters](#multiple-adapters) + - [HTTP & TLS](#http--tls) + - [MQTT](#mqtt) + - [Ruby Extensions](#ruby-extensions) +- [Sample Outputs](#sample-outputs) +- [Directory Structure](#directory-structure) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [Version History](#version-history) +- [Useful Links](#useful-links) +- [License](#license) -Version 2.5.0 Support for version 2.5. Added validation of observations in the stream and WebSockets support. +--- -Version 2.4.0 Added support for version 2.4 +# 🧭 Overview -Version 2.3.0 Support for all Version 2.3 standard changes and JSON ingress to MQTT adapter. +The MTConnect Agent acts as the intermediary between raw machine data and higher-level software systems that require structured, contextualized insights. It transforms messy, vendor-specific machine signals into clean, semantic MTConnect XML/JSON outputs. The agent follows a one-way, read-only information model, meaning it never sends instructions back to machines—making it safe and easy to integrate into existing manufacturing setups. -Version 2.2.0 Support for all Version 2.2 standard changes and dynamic configuration from adapters. Upgrade to conan 2. +By handling multiple adapters, buffering high-frequency data, supporting secure communication, and offering real-time streaming capabilities, the agent is suitable for both small machine shops and enterprise-level Industry 4.0 deployments. -Version 2.1.0 Added MQTT Sink, Agent Restart and new JSON format (version 2) +--- -Version 2.0.0 Rearchitecture of the agent with TLS, MQTT Adapter, Ruby Interpreter, Agent Adaptr, and much more +# ⭐ Key Features -Version 1.7.0 added kinematics, solid models, and new specifications types. +### 🔌 Wide Input Support +- SHDR adapters for CNC interoperability +- MQTT ingestion for IoT environments +- Native JSON input for modern sensors and custom connectors -Version 1.6.0 added coordinate systems, specifications, and tabular data. +### 🌐 Flexible Output Options +- HTTP server for `/probe`, `/current`, `/sample`, `/assets` +- WebSocket streaming for real-time dashboards +- TLS support for secure communication -Version 1.5.0 added Data Set capabilities and has been updated to use C++ 14. +### 🧠 Extensibility +- Ruby-based scripting for custom transformation pipelines +- Modular components allowing custom adapters +- Namespace support for extended functionality -Version 1.4.0 added time period filter constraint, compositions, initial values, and reset triggers. +### ⚙️ Industrial-Grade Infrastructure +- High-performance circular buffer for storing samples +- Cross-platform support (Windows, Linux, macOS) +- Low-overhead runtime for embedded or resource-constrained machines -Version 1.3.0 added the filter constraints, references, cutting tool archetypes, and formatting styles. +--- -Version 1.2.0 added the capability to support assets. +# 🧱 Architecture +The agent transforms heterogeneous raw machine signals into structured, queryable MTConnect data streams. This architecture ensures consistent data modeling regardless of vendor or device type. -Version 1.1.0.8 add the ability to run the C++ Agent as a Windows -service and support for a configuration file instead of command line -arguments. The agent can accept input from a socket in a pipe (`|`) -delimited stream according to the descriptions given in the adapter -guide. - -The current win32 binary is built statically and requires no -additional dlls to run. This allows for a single exe distributable. - -Usage ------- - - agent [help|install|debug|run] [configuration_file] - help Prints this message - install Installs the service - remove Remove the service - debug Runs the agent on the command line with verbose logging - - sets logging_level to debug - run Runs the agent on the command line - config_file The configuration file to load - Default: agent.cfg in current directory - -When the agent is started without any arguments it is assumed it will -be running as a service and will begin the service initialization -sequence. The full path to the configuration file is stored in the -registry in the following location: - - \\HKEY_LOCAL_MACHINE\SOFTWARE\MTConnect\MTConnect Agent\ConfigurationFile - -Directories ------ - -* `agent/` - This contains the application source files and CMake file. - -* `assets/` - Some example Cutting Tool asset files. - -* `lib/` - Third party library source. Contains source for cppunit, dlib, and libxml++. - -* `samples/` - Sample XML configuration files mainly used for tests. - -* `simulator/` - Ruby scripts to execute an adapter simulator, both command-line and log-replay. - -* `test/` - Various unit tests. - -* `tools/` - Ruby scripts to dump the agent and the adapter in SHDR format. Includes a sequence - test script. - -* `unix/` - Unix init.d script - -* `win32/` - Libraries required only for the win32 build and the win32 solution. - -Windows Binary Release ------ - -The windows binary releases come with a prebuilt exe that is -statically linked with the Microsoft Runtime libraries. Aside from the -standard system libraries, the agent only requires winsock -libraries. The agent has been test with version of Windows 2000 and -later. - -* `bin/` - Win32 binary (no dependencies required). - -Building -------- - -Platform specific instructions are at the end of the README. - -Configuration ------- - -The configuration file is using the standard Boost C++ file -format. The configuration file format is flexible and allows for both -many adapters to be served from one agent and an adapter to feed -multiple agents. The format is: - - Key = Value - -The key can only occur once within a section and the value can be any -sequence of characters followed by a ``. There no significance to -the order of the keys, so the file can be specified in free form. We -will go over some configurations from a minimal configuration to more -complex multi-adapter configurations. - -### Serving Static Content ### - -Using a Files Configuration section, individual files or directories can be inserted -into the request file space from the agent. To do this, we use the Files top level -configuration declaration as follows: - - Files { - schemas { - Path = ../schemas - Location = /schemas/ - } - styles { - Path = ../styles - Location = /styles/ - } - } - -Each set of files must be declared using a named file description, like schema or -styles and the local `Path` and the `Location` the files will be mapped to in the -HTTP server namespace. For example: - - http://example.com:5000/schemas/MTConnectStreams_2.0.xsd will map to ../schemas/MTConnectStreams_2.0.xsd - -All files will be mapped and the directory names do not need to be the same. These files can be either served directly or can be used to extend the schema or add XSLT stylesheets for formatting the XML in browsers. - -### Specifying the Extended Schemas ### - -To specify the new schema for the documents, use the following declaration: - - StreamsNamespaces { - e { - Urn = urn:example.com:ExampleStreams:2.0 - Location = /schemas/ExampleStreams_2.0.xsd - } - } - -This will use the ExampleStreams_2.0.xsd schema in the document. The `e` is the alias that will be -used to reference the extended schema. The `Location` is the location of the xsd file relative in -the agent namespace. The `Location` must be mapped in the `Files` section. - -An optional `Path` can be added to the `...Namespaces` declaration instead of declaring the -`Files` section. The `Files` makes it easier to include multiple files from a directory and will -automatically include all the default MTConnect schema files for the correct version. -(See `SchemaVersion` option below) - -You can do this for any one of the other documents: - - StreamsNamespaces - DevicesNamespaces - AssetsNamespaces - ErrorNamespaces - -### Specifying the XML Document Style ### - -The same can be done with style sheets, but only the Location is required. - - StreamsStyle { - Location = /styles/Streams.xsl - } - -An optional `Path` can also be used to reference the xsl file directly. This will not -include other files in the path like css or included xsl transforms. It is advised to -use the `Files` declaration. - -The following can also be declared: - - DevicesStyle - StreamsStyle - AssetsStyle - ErrorStyle - -### Example 1: ### - -Here’s an example configuration file. The `#` character can be used to -comment sections of the file. Everything on the line after the `#` is -ignored. We will start with an extremely simple configuration file. - - # A very simple file... - Devices = VMC-3Axis.xml - -This is a one line configuration that specifies the XML file to load -for the devices. Since all the values are defaulted, an empty -configuration file can be specified. This configuration file will load -the `VMC-3Axis.xml` file and try to connect to an adapter located on the -localhost at port 7878. `VMC-3Axis.xml` must only contain the definition -for one device; if more devices exist, an error will be raised and the -process will exit. - -### Example 2: ### - -Most configuration files will specify at least one adapter. The -adapters are contained within a block. The Boost configuration file -format allows for nested configurations and block associations. There -are a number of configurations that can be given for each -adapter. Multiple adapters can be specified for one device as well. - - Devices = VMC-3Axis.xml - - Adapters - { - VMC-3Axis - { - Host = 192.168.10.22 - Port = 7878 # *Default* value... - } - } - -This example loads the devices file as before, but specifies the list -of adapters. The device is taken from the name `VMC-3Axis` that starts -the nested block and connects to the adapter on `192.168.10.22` and port -`7878`. Another way of specifying the equivalent configuration is: - - Devices = VMC-3Axis.xml - - Adapters - { - Adapter_1 - { - Device = VMC-3Axis - Host = 192.168.10.22 - Port = 7878 # *Default* value... - } - } - -Line 7 specifies the Device name associated with this adapter -explicitly. We will show how this is used in the next example. - -### Example 3: ### - -Multiple adapters can supply data to the same device. This is done by -creating multiple adapter entries and specifying the same Device for -each. - - Devices = VMC-3Axis.xml - - Adapters - { - Adapter_1 - { - Device = VMC-3Axis - Host = 192.168.10.22 - Port = 7878 # *Default* value... - } - - # Energy sensor - Adapter_2 - { - Device = VMC-3Axis - Host = 192.168.10.2 - Port = 7878 # *Default* value... - } - } - -Both `Adapter_1` and `Adapter_2` will feed the `VMC-3Axis` device with -different data items. The `Adapter_1` name is arbitrary and could just -as well be named `EnergySensor` if desired as illustrated below. - - Devices = VMC-3Axis.xml - - Adapters - { - Controller - { - Device = VMC-3Axis - Host = 192.168.10.22 - Port = 7878 # *Default* value... - } - - EnergySensor - { - Device = VMC-3Axis - Host = 192.168.10.2 - Port = 7878 # *Default* value... - } - } - -### Example 4: ### - -In this example we change the port to 80 which is the default http port. -This also allows HTTP PUT from the local machine and 10.211.55.2. - - Devices = MyDevices.xml - Port = 80 - AllowPutFrom = localhost, 10.211.55.2 - - Adapters - { - ... - -For browsers you will no longer need to specify the port to connect to. - -### Example 5: ### - -If multiple devices are specified in the XML file, there must be an -adapter feeding each device. - - Devices = MyDevices.xml - - Adapters - { - VMC-3Axis - { - Host = 192.168.10.22 - } - - HMC-5Axis - { - Host = 192.168.10.24 - } - } - -This will associate the adapters for these two machines to the VMC -and HMC devices in `MyDevices.xml` file. The ports are defaulted to -7878, so we are not required to specify them. - -### Example 6: ### - -In this example we demonstrate how to change the service name of the agent. This -allows a single machine to run multiple agents and/or customize the name of the service. -Multiple configuration files can be created for each service, each with a different -ServiceName. The configuration file must be referenced as follows: - - C:> agent install myagent.cfg - -If myagent.cfg contains the following statements: - - Devices = MyDevices.xml - ServiceName = MTC Agent 1 - - Adapters - { - ... - - -The service will now be displayed as "MTC Agent 1" as opposed to "MTConnect Agent" -and it will automatically load the contents of myagent.cfg with it starts. You can now -use the following command to start this from a command prompt: - - C:> net start "MTC Agent 1" - -To remove the service, do the following: - - C:> agent remove myagent.cfg - -### Example 7: ### - -Logging configuration is specified using the `logger_config` block. You -can change the `logging_level` to specify the verbosity of the logging -as well as the destination of the logging output. - - logger_config - { - logging_level = debug - output = file debug.log - } - -This will log everything from debug to fatal to the file -debug.log. For only fatal errors you can specify the following: - - logger_config - { - logging_level = fatal - } - -The default file is agent.log in the same directory as the agent.exe -file resides. The default logging level is `info`. To have the agent log -to the command window: - - logger_config - { - logging_level = debug - output = cout - } - -This will log debug level messages to the current console window. When -the agent is run with debug, it sets the logging configuration to -debug and outputs to the standard output as specified above. - -### Example: 8 ### - -The MTConnect C++ Agent supports extensions by allowing you to specify your -own XSD schema files. These files must include the MTConnect schema and the top -level node is required to be MTConnect. The "x" in this case is the namespace. You -MUST NOT use the namespace "m" since it is reserved for MTConnect. See example 9 -for an example of changing the MTConnect schema file location. - -There are four namespaces in MTConnect: Devices, Streams, Assets, and Error. In -this example we will replace the Streams and Devices namespace with our own namespace -so we can have validatable XML documents. - - StreamsNamespaces { - x { - Urn = urn:example.com:ExampleStreams:1.2 - Location = /schemas/ExampleStreams_1.2.xsd - Path = ./ExampleStreams_1.2.xsd - } - } - - DevicesNamespaces { - x { - Urn = urn:example.com:ExampleDevices:1.2 - Location = /schemas/ExampleDevices_1.2.xsd - Path = ./ExampleDevices_1.2.xsd - } - } - -For each schema file we have three options we need to specify. The Urn -is the urn in the schema file that will be used in the header. The Location -is the path specified in the URL when requesting the schema file from the -HTTP client and the Path is the path on the local file system. - -### Example: 9 ### - -If you only want to change the schema location of the MTConnect schema files and -serve them from your local agent and not from the default internet location, you -can use the namespace "m" and give the new schema file location. This MUST be the -MTConnect schema files and the urn will always be the MTConnect urn for the "m" -namespace -- you cannot change it. - - StreamsNamespaces { - m { - Location = /schemas/MTConnectStreams_2.0.xsd - Path = ./MTConnectStreams_2.0.xsd - } - } - - DevicesNamespaces { - m { - Location = /schemas/MTConnectDevices_2.0.xsd - Path = ./MTConnectDevices_2.0.xsd - } - } - -The MTConnect agent will now serve the standard MTConnect schema files -from the local directory using the schema path /schemas/MTConnectDevices_2.0.xsd. - - -### Example: 10 ### - -We can also serve files from the MTConnect Agent as well. In this example -we can assume we don't have access to the public internet and we would still -like to provide the MTConnect streams and devices files but have the MTConnect -Agent serve them up locally. - - DevicesNamespaces { - x { - Urn = urn:example.com:ExampleDevices:2.0 - Location = /schemas/ExampleDevices_2.0.xsd - Path = ./ExampleDevices_2.0.xsd - } - - Files { - stream { - Location = /schemas/MTConnectStreams_2.0.xsd - Path = ./MTConnectStreams_2.0.xsd - } - device { - Location = /schemas/MTConnectDevices_2.0.xsd - Path = ./MTConnectDevices_2.0.xsd - } - } - -Or use the short form for all files: - - Files { - schemas { - Location = /schemas/MTConnectStreams_2.0.xsd - Path = ./MTConnectStreams_2.0.xsd - } - } - -If you have specified in your xs:include schemaLocation inside the -ExampleDevices_2.0.xsd file the location "/schemas/MTConnectStreams_2.0.xsd", -this will allow it to be served properly. This can also be done using the -Devices namespace: - - DevicesNamespaces { - m { - Location = /schemas/MTConnectDevices_2.0.xsd - Path = ./MTConnectDevices_2.0.xsd - } - } - -The MTConnect agent will allow you to serve any other files you wish as well. You -can specify a new static file you would like to deliver: - - Files { - myfile { - Location = /files/xxx.txt - Path = ./files/xxx.txt - } - -The agent will not serve all files from a directory and will not provide an index -function as this is insecure and not the intended function of the agent. - -Ruby ---------- - -If the "-o with_ruby=True" build is selected, then use to following configuration: - - Ruby { - module = path/to/module.rb - } - -The module specified at the path given will be loaded. There are examples in the test/Resources/ruby directory in -github: [Ruby Tests](https://github.com/mtconnect/cppagent/tree/master/test/resources/ruby). - -The current functionality is limited to the pipeline transformations from the adapters. Future changes will include adding sources and sinks. - -The following is a complete example for fixing the Execution of a machine: - -```ruby -class AlertTransform < MTConnect::RubyTransform - def initialize(name, filter) - @cache = Hash.new - super(name, filter) - end - - @@count = 0 - def transform(obs) - @@count += 1 - if @@count % 10000 == 0 - puts "---------------------------" - puts "> #{ObjectSpace.count_objects}" - puts "---------------------------" - end - - dataItemId = obs.properties[:dataItemId] - if dataItemId == 'servotemp1' or dataItemId == 'Xfrt' or dataItemId == 'Xload' - @cache[dataItemId] = obs.value - device = MTConnect.agent.default_device - - di = device.data_item('xaxisstate') - if @cache['servotemp1'].to_f > 10.0 or @cache['Xfrt'].to_f > 10.0 or @cache['Xload'].to_f > 10 - newobs = MTConnect::Observation.new(di, "ERROR") - else - newobs = MTConnect::Observation.new(di, "OK") - end - forward(newobs) - end - forward(obs) - end -end - -MTConnect.agent.sources.each do |s| - pipe = s.pipeline - puts "Splicing the pipeline" - trans = AlertTransform.new('AlertTransform', :Sample) - puts trans - pipe.splice_before('DeliverObservation', trans) -end +``` + +-------------------------+ + | Client Layer | + | MES, Dashboards, Apps | + +-----------+-------------+ + | + HTTP / WebSockets / TLS + | + +----------v----------+ + | MTConnect Agent | + | (C++ Reference) | + +-----+--------+------+ + | | + Normalization & Buffering + | + +--------------+-----------------+ + | | + SHDR Adapters MQTT Broker + | | +Raw Machine Signals Sensor/Device Messages ``` -Configuration Parameters ---------- - -### Top level configuration items #### - -* `AgentDeviceUUID` - Set the UUID of the agent device - - *Default*: UUID derived from the IP address and port of the agent - -* `BufferSize` - The 2^X number of slots available in the circular - buffer for samples, events, and conditions. - - *Default*: 17 -> 2^17 = 131,072 slots. - -* `CheckpointFrequency` - The frequency checkpoints are created in the - stream. This is used for current with the at argument. This is an - advanced configuration item and should not be changed unless you - understand the internal workings of the agent. - - *Default*: 1000 - -* `CreateUniqueIds`: Changes all the ids in each element to a UUID that will be unique across devices. This is used for merging devices from multiple sources. - - *Default*: `false` - -* `Devices` - The XML file to load that specifies the devices and is - supplied as the result of a probe request. If the key is not found - the defaults are tried. - - *Defaults*: probe.xml or Devices.xml - -* `DisableAgentDevice` - When the schema version is >= 1.7, disable the - creation of the Agent device. - - *Default*: false - -* `JsonVersion` - JSON Printer format. Old format: 1, new format: 2 - - *Default*: 2 - -* `LogStreams` - Debugging flag to log the streamed data to a file. Logs to a file named: `Stream_` + timestamp + `.log` in the current working directory. This is only for the Rest Sink. - - *Default*: `false` - -* `MaxAssets` - The maximum number of assets the agent can hold in its buffer. The - number is the actual count, not an exponent. - - *Default*: 1024 - -* `MaxCachedFileSize` - The maximum size of a raw file to cache in memory. - - *Default*: 20 kb - -* `MinCompressFileSize` - The file size where we begin compressing raw files sent to the client. - - *Default*: 100 kb - -* `MinimumConfigReloadAge` - The minimum age of a config file before an agent reload is triggered (seconds). - - *Default*: 15 seconds - -* `MonitorConfigFiles` - Monitor agent.cfg and Devices.xml files and restart agent if they change. - - *Default*: false - -* `MonitorInterval` - The interval between checks if the agent.cfg or Device.xml files have changed. - - *Default*: 10 seconds - -* `Pretty` - Pretty print the output with indententation - - *Default*: false - -* `PidFile` - UNIX only. The full path of the file that contains the - process id of the daemon. This is not supported in Windows. - - *Default*: agent.pid - -* `SchemaVersion` - Change the schema version to a different version number. - - *Default*: 2.0 - -* `Sender` - The value for the sender header attribute. - - *Default*: Local machine name - -* `ServiceName` - Changes the service name when installing or removing - the service. This allows multiple agents to run as services on the same machine. - - *Default*: MTConnect Agent - -* `SuppressIPAddress` - Suppress the Adapter IP Address and port when creating the Agent Device ids and names. This applies to all adapters. - - *Default*: `false` - -* `VersionDeviceXml` - Create a new versioned file every time the Device.xml file changes from an external source. - - *Default*: `false` - -* `CorrectTimestamps` - Verify time is always progressing forward for each data item and correct if not. - - *Default*: `false` - -* `Validation` - Turns on validation of model components and observations - - *Default*: `false` - -* `WorkerThreads` - The number of operating system threads dedicated to the Agent - - *Default*: 1 - -#### Adapter General Configuration - -These can be overridden on a per-adapter basis - -* `Protocol` –Specify protocol. Options: [`shdr`|`mqtt`] - - *Default*: shdr - -* `ConversionRequired` - Global default for data item units conversion in the agent. - Assumes the adapter has already done unit conversion. - - *Default*: true - -* `EnableSourceDeviceModels` - Allow adapters and data sources to supply Device - configuration - - *Default*: false - -* `Heartbeat` – Overrides the heartbeat interval sent back from the adapter in the - `* PONG `. The heartbeat will always be this value in milliseconds. - - *Default*: _None_ - -* `IgnoreTimestamps` - Overwrite timestamps with the agent time. This will correct - clock drift but will not give as accurate relative time since it will not take into - consideration network latencies. This can be overridden on a per adapter basis. - - *Default*: false - -* `LegacyTimeout` - The default length of time an adapter can be silent before it - is disconnected. This is only for legacy adapters that do not support heartbeats. - - *Default*: 600 - -* `PreserveUUID` - Do not overwrite the UUID with the UUID from the adapter, preserve - the UUID in the Devices.xml file. This can be overridden on a per adapter basis. - - *Default*: true - -* `ReconnectInterval` - The amount of time between adapter reconnection attempts. - This is useful for implementation of high performance adapters where availability - needs to be tracked in near-real-time. Time is specified in milliseconds (ms). - - *Default*: 10000 - -* `ShdrVersion` - Specifies the SHDR protocol version used by the adapter. When greater than one (1), - allows multiple complex observations, like `Condition` and `Message` on the same line. If it equials one (1), - then any observation requiring more than a key/value pair need to be on separate lines. This is the default for all adapters. - - *Default*: 1 - -* `UpcaseDataItemValue` - Always converts the value of the data items to upper case. - - *Default*: true - -#### REST Service Configuration - -* `AllowPut` - Allow HTTP PUT or POST of data item values or assets. - - *Default*: false - -* `AllowPutFrom` - Allow HTTP PUT or POST from a specific host or - list of hosts. Lists are comma (,) separated and the host names will - be validated by translating them into IP addresses. - - *Default*: none - -* `HttpHeaders` - Additional headers to add to the HTTP Response for CORS Security - - > Example: ``` - > HttpHeaders { - > Access-Control-Allow-Origin = * - > Access-Control-Allow-Methods = GET - > Access-Control-Allow-Headers = Content-Type - > }``` - -* `Port` - The port number the agent binds to for requests. - - *Default*: 5000 - -* `ServerIp` - The server IP Address to bind to. Can be used to select the interface in IPV4 or IPV6. - - *Default*: 0.0.0.0 - - -#### Configuration Pameters for TLS (https) Support #### - -The following parameters must be present to enable https requests. If there is no password on the certificate, `TlsCertificatePassword` may be omitted. - -* `TlsCertificateChain` - The name of the file containing the certificate chain created from signing authority - - *Default*: *NULL* - -* `TlsCertificatePassword` - The password used when creating the certificate. If none was supplied, do not use. - - *Default*: *NULL* - -* `TlsClientCAs` - For `TlsVerifyClientCertificate`, specifies a file that contains additional certificate authorities for verification - - *Default*: *NULL* - -* `TlsDHKey` - The name of the file containing the Diffie–Hellman key - - *Default*: *NULL* - -* `TlsOnly` - Only allow secure connections, http requests will be rejected - - *Default*: `false` - -* `TlsPrivateKey` - The name of the file containing the private key for the certificate - - *Default*: *NULL* - -* `TlsVerifyClientCertificate` - Request and verify the client certificate against root authorities - - *Default*: false - -### MQTT Configuration - -* `MqttCaCert` - CA Certificate for MQTT TLS connection to the MTT Broker - - *Default*: *NULL* - -* `MqttHost` - IP Address or name of the MQTT Broker - - *Default*: 127.0.0.1 - -* `MqttPort` - Port number of MQTT Broker - - *Default*: 1883 - -* `MqttTls` - TLS Certificate for secure connection to the MQTT Broker - - *Default*: `false` - -* `MqttWs` - Instructs MQTT to connect using web sockets - - *Default*: `false` - -#### MQTT Sink +This architecture is designed to be modular, allowing organizations to gradually expand their MTConnect ecosystem without reconfiguring existing systems. -> **⚠️ Deprecation Notice:** The origional `MqttService` has been deprecated. `MqttService` is now an alias for `Mqtt2Service`. Please use `MqttService` for new configurations. `Mqtt2Service` will continue to work for backward compatibility but may be removed in a future version. +--- -**Deprecated Configuration (still supported):** +# ⚡ Quick Start -Enabled in `agent.cfg` by specifying: +This section helps developers get up and running with the agent in minutes. -``` -Sinks { - MqttService { - # Configuration Options below... - } -} +### 1. Clone the Repository +```bash +git clone https://github.com/mtconnect/cppagent.git +cd cppagent ``` -> **Note:** Both `MqttService` and `Mqtt2Service` use the same underlying implementation and support the same configuration options listed below. - -#### MQTT Sink Configuration +### 2. Build Using Conan +```bash +conan create . --build=missing +``` -Enabled in `agent.cfg` by specifying: +### 3. Create a Minimal Configuration +```txt +Devices = Devices.xml +Port = 5000 -``` -Sinks { - Mqtt2Service { - # Configuration Options... +Adapters { + MyCNC { + Host = localhost + Port = 7878 } } ``` -* `AssetTopic` - Prefix for the Assets - - *Default*: `MTConnect/Asset/[device]` - -* `CurrentTopic` - Prefix for the Current - - *Default*: `MTConnect/Current/[device]` - -* `ProbeTopic` or `DeviceTopic` - Prefix for the Device Model topic - - > Note: `[device]` will be replaced with the uuid of each device. Other patterns can be created, - > for example: `MTConnect/[device]/Probe` will group by device instead of operation. - > `DeviceTopic` will also work. - - *Default*: `MTConnect/Probe/[device]` - -* `SampleTopic` - Prefix for the Sample - - *Default*: `MTConnect/Sample/[device]` - -* `MqttLastWillTopic` - The topic used for the last will and testement for an agent - - > Note: The value will be `AVAILABLE` when the Agent is publishing and connected and will - > publish `UNAVAILABLE` when the agent disconnects from the broker. - - *Default*: `MTConnect/Probe/[device]/Availability"` - -* `MqttCurrentInterval` - The frequency to publish currents. Acts like a keyframe in a video stream. - - *Default*: 10000ms - -* `MqttSampleInterval` - The frequency to publish samples. Works the same way as the `interval` in the rest call. Groups observations up and publishes with the minimum interval given. If nothing is availble, will wait until an observation arrives to publish. - - *Default*: 500ms - -* `MqttSampleCount` - The maxmimum number of observations to publish at one time. - - *Default*: 1000 - -* `MqttRetain` - For the MQTT Sinks, sets the retain flag for publishing. - - *Default*: True - -* `MqttQOS`: - For the MQTT Sinks, sets the Quality of Service. Must be one of `at_least_once`, `at_most_once`, `exactly_once`. - - *Default*: `at_least_once` - -* `MqttXPath`: - The xpath filter to apply to all current and samples published to MQTT. If the XPath is invalid, it will fall back to publishing all data items. - - *Default*: All data items - -## MQTT Entity Sink Documentation - -For detailed configuration, usage, and message format for the MQTT Entity Sink, see: [docs: MTConnect MQTT Entity Sink](src/mtconnect/sink/mqtt_entity_sink/README.md) - -### Adapter Configuration Items ### - -* `Adapters` - Contains a list of device blocks. If there are no Adapters - specified and the Devices file contains one device, the Agent defaults - to an adapter located on the localhost at port 7878. Data passed from - the Adapter is associated with the default device. - - *Default*: localhost 7878, associated with the default device - - * `Device` - The name of the device that corresponds to the name of - the device in the Devices file. Each adapter can map to one - device. Specifying a "*" will map to the default device. - - *Default*: The name of the block for this adapter or if that is - not found the default device if only one device is specified - in the devices file. - - * `Host` - The host the adapter is located on. - - *Default*: localhost - - * `Port` - The port to connect to the adapter. - - *Default*: 7878 - - * `Manufacturer` - Replaces the manufacturer attribute in the device XML. - - *Default*: Current value in device XML. - - * `Station` - Replaces the Station attribute in the device XML. - - *Default*: Current value in device XML. - - * `SerialNumber` - Replaces the SerialNumber attribute in the device XML. - - *Default*: Current value in device XML. - - * `UUID` - Replaces the UUID attribute in the device XML. - - *Default*: Current value in device XML. - - * `AutoAvailable` - For devices that do not have the ability to provide available events, if `yes`, this sets the `Availability` to AVAILABLE upon connection. - - *Default*: no (new in 1.2, if AVAILABILITY is not provided for device it will be automatically added and this will default to yes) - - * `AdditionalDevices` - Comma separated list of additional devices connected to this adapter. This provides availability support when one adapter feeds multiple devices. - - *Default*: nothing - - * `FilterDuplicates` - If value is `yes`, filters all duplicate values for data items. This is to support adapters that are not doing proper duplicate filtering. - - *Default*: no - - * `LegacyTimeout` - length of time an adapter can be silent before it - is disconnected. This is only for legacy adapters that do not support - heartbeats. If heartbeats are present, this will be ignored. - - *Default*: 600 - - * `ReconnectInterval` - The amount of time between adapter reconnection attempts. - This is useful for implementation of high performance adapters where availability - needs to be tracked in near-real-time. Time is specified in milliseconds (ms). - Defaults to the top level ReconnectInterval. - - *Default*: 10000 - - * `IgnoreTimestamps` - Overwrite timestamps with the agent time. This will correct - clock drift but will not give as accurate relative time since it will not take into - consideration network latencies. This can be overridden on a per adapter basis. - - *Default*: Top Level Setting - - * `PreserveUUID` - Do not overwrite the UUID with the UUID from the adapter, preserve - the UUID in the Devices.xml file. This can be overridden on a per adapter basis. - - *Default*: false - - * `RealTime` - Boost the thread priority of this adapter so that events are handled faster. - - *Default*: false - - * `RelativeTime` - The timestamps will be given as relative offsets represented as a floating - point number of milliseconds. The offset will be added to the arrival time of the first - recorded event. If the timestamp is given as a time, the difference between the agent time - and the fitst incoming timestamp will be used as a constant clock adjustment. - - *Default*: false - - * `ConversionRequired` - Adapter setting for data item units conversion in the agent. - Assumes the adapter has already done unit conversion. Defaults to global. - - *Default*: Top Level Setting - - * `UpcaseDataItemValue` - Always converts the value of the data items to upper case. - - *Default*: Top Level Setting - - * `ShdrVersion` - Specifies the SHDR protocol version used by the adapter. When greater than one - (1), allows multiple complex observations, like `Condition` and `Message` on the same line. - If it equials one (1), then any observation requiring more than a key/value pair need to be on - separate lines. Applies to only this adapter. - - *Default*: 1 - - * `SuppressIPAddress` - Suppress the Adapter IP Address and port when creating the Agent Device ids and names. - - *Default*: false - - * `AdapterIdentity` - Adapter Identity name used to prefix dataitems within the Agent device ids and names. - - *Default*: - * If `SuppressIPAddress` == false:\ - `AdapterIdentity` = ```_ {IP}_{PORT}```\ - example:`_localhost_7878` - - * If `SuppressIPAddress` == true:\ - `AdapterIdentity` = ```_ sha1digest({IP}_{PORT})```\ - example: `__71020ed1ed` - -#### MQTT Adapter/Source - -* `MqttHost` - IP Address or name of the MQTT Broker - - *Default*: 127.0.0.1 - -* `MqttPort` - Port number of MQTT Broker - - *Default*: 1883 - -* `topics` - list of topics to subscribe to. Note : Only raw SHDR strings supported at this time - - *Required* - -* `MqttClientId` - Port number of MQTT Broker - - *Default*: Auto-generated - - > **⚠️Note:** Mqtt Sinks and Mqtt Adapters create separate connections to their respective brokers, but currently use the same client ID by default. Because of this, when using a single broker for source and sink, best practice is to explicitly specify their respective `MqttClientId` - > - - Example mqtt adapter block: - ```json - mydevice { - Protocol = mqtt - MqttHost = localhost - MqttPort = 1883 - MqttClientId = myUniqueID - Topics = /ingest - } - ``` - -* `AdapterIdentity` - Adapter Identity name used to prefix dataitems within the Agent device ids and names. - - *Default*: - * If `SuppressIPAddress` == false:\ - `AdapterIdentity` = ```_ {IP}_{PORT}```\ - example:`_localhost_7878` - - * If `SuppressIPAddress` == true:\ - `AdapterIdentity` = ```_ sha1digest({IP}_{PORT})```\ - example: `__71020ed1ed` - -### Agent Adapter Configuration - -* `Url` - The URL of the source agent. `http:` or `https:` are accepted for the protocol. -* `SourceDevice` – The Device name or UUID for the source of the data -* `Count` – the number of items request during a single sample - - *Default*: 1000 - -* `Polling Interval` – The interval used for streaming or polling in milliseconds - - *Default*: 500ms - -* `Reconnect Interval` – The interval between reconnection attampts in milliseconds - - *Default*: 10000ms - -* `Use Polling` – Force the adapter to use polling instead of streaming. Only set to `true` if x-multipart-replace blocked. - - *Default*: false - -* `Heartbeat` – The heartbeat interval from the server - - *Default*: 10000ms - -logger_config configuration items ------ - -* `logger_config` - The logging configuration section. - - * `logging_level` - The logging level: `trace`, `debug`, `info`, `warn`, - `error`, or `fatal`. - - *Default*: `info` - - Note: when running Agent with `agent debug`, `logging_level` will be set to `debug`. - - * `output` - The output file or stream. If using a file, specify - as: `"file "`. cout and cerr can be used to specify the - standard output and standard error streams. *Default*s to the same - directory as the executable. - - *Default*: file `adapter.log` - - * `max_size` - The maximum log file size. Suffix can be K for kilobytes, M for megabytes, or - G for gigabytes. No suffix will default to bytes (B). Case is ignored. - - *Default*: 10M - - * `max_index` - The maximum number of log files to keep. - - *Default*: 9 - - * `schedule` - The scheduled time to start a new file. Can be DAILY, WEEKLY, or NEVER. - - *Default*: NEVER - -# Agent-Adapter Protocols -## SHDR Version 2.0 - -The principle adapter data format is a simple plain text stream separated by the pipe character `|`. Every line except for commands starts with an optional timestamp in UTC. If the timestamp is not supplied the agent will supply a timestamp of its own taken at the arrival time of the data to the agent. The remainder of the line is a key followed by data – depending on the type of data item is being written to. - -A simple set of events and samples will look something like this: - - 2009-06-15T00:00:00.000000|power|ON|execution|ACTIVE|line|412|Xact|-1.1761875153|Yact|1766618937 - -A line is a sequence of fields separated by `|`. The simplest requires one key/value pair. The agent will discard any lines where the data is malformed. The end must end with a LF (ASCII 10) or CR-LF (ASCII 15 followed by ASCII 10) (UNIX or Windows conventions respectively). The key will map to the data item using the following items: the `id` attribute, the `name` attribute, and the `CDATA` of the `Source` element. If the key does not match it will be rejected and the agent will log the first time it fails. Different data items categories and types require a different number of tokens. The following rules specify the requirements for the adapter tokens: - -If the value itself contains a pipe character `|` the pipe must be escaped using a leading backslash `\`. In addition the entire value has to be wrapped in quotes: - - 2009-06-15T00:00:00.000000|description|"Text with \| (pipe) character." - -Conditions require six (6) fields as follows: - - |||||| - - Condition id and native code are set to the same value given as - - |||:||| - - Condition id is set to condition_id and native code is set to native_code - - |||||| - - Condition id is set to condition_id and native code is not set - - -For a complete description of these fields, see the standard. An example line will look like this: +### 4. Run the Agent +```bash +./agent run agent.cfg +``` - 2014-09-29T23:59:33.460470Z|htemp|WARNING|HTEMP-1-HIGH|HTEMP|1|HIGH|Oil Temperature High +### 5. Verify Output +- http://localhost:5000/probe +- http://localhost:5000/current +- http://localhost:5000/sample -The next special format is the Message. There is one additional field, native_code, which needs to be included: +--- - 2014-09-29T23:59:33.460470Z|message|CHG_INSRT|Change Inserts +# 🛠 Installation -Time series data also gets special treatment, the count and optional frequency are specified. In the following example we have 10 items at a frequency of 100hz: +The agent uses CMake + Conan for dependency management, making cross-platform builds consistent. - 2014-09-29T23:59:33.460470Z|current|10|100|1 2 3 4 5 6 7 8 9 10 +## Linux Build +Install prerequisites: +```bash +sudo apt-get install build-essential cmake libssl-dev libcurl4-openssl-dev +``` +Build: +```bash +conan install . --build=missing +cmake --preset conan-release +cmake --build build-release +``` -The data item name can also be prefixed with the device name if this adapter is supplying data to multiple devices. The following is an example of a power meter for three devices named `device1`, `device2`, and `device3`: +--- - 2014-09-29T23:59:33.460470Z|device1:current|12|device2:current|11|device3:current|10 +## Windows Build +- Install Visual Studio 2022 +- Install CMake + Conan +```powershell +conan install . --build=missing +cmake --preset conan-release +cmake --build build-release --config Release +``` -All data items follow the formatting requirements in the MTConnect standard for the vocabulary and coordinates like PathPosition. +--- -A new feature introduced in version 1.4 is the ability to announce a reset has been triggered. If we have a part count named `pcount` that gets reset daily, the new protocol is as follows: +## macOS Build +macOS is ideal for development and prototyping. +```bash +brew install cmake openssl conan +conan install . --build=missing +cmake --preset conan-release +cmake --build build-release +``` - 2014-09-29T23:59:33.460470Z|pcount|0:DAY +--- -To specify the duration of the static, indicate it with an `@` sign after the timestamp as follows: +# 🔧 Basic Configuration - 2014-09-29T23:59:33.460470Z@100.0|pcount|0:DAY +The agent is controlled via two primary files: -### `DATA_SET` Representation ### +### **1. Devices.xml** +Defines the machine structure, including components and DataItems. +This file gives meaning to raw data received by adapters. -A new feature in version 1.5 is the `DATA_SET` representation which allows for key value pairs to be given. The protocol is similar to time series where each pair is space delimited. The agent automatically removes duplicate values from the stream and allows for addition, deletion and resetting of the values. The format is as follows: +### **2. agent.cfg** +Sets up communication ports, adapters, buffer sizes, logging, security, and optional features. - 2014-09-29T23:59:33.460470Z|vars|v1=10 v2=20 v3=30 +A minimal configuration looks like: -This will create a set of three values. To remove a value +Example: +``` +Devices = Devices.xml +Port = 5000 +AllowPut = true +Buffersize = 131072 +``` - 2014-09-29T23:59:33.460470Z|vars|v2 v3= +--- -This will remove v2 and v3. If text after the equal `=` is empty or the `=` is not given, the value is deleted. To clear the set, specify a `resetTriggered` value such as `MANUAL` or `DAY` by preceeding it with a colon `:` at the beginning of the line. +# 🔧 Advanced Configuration - 2014-09-29T23:59:33.460470Z|vars|:MANUAL +## **Overview of Advanced Features** -This will remove all the values from the current set. The set can also be reset to a specific set of values: +Some capabilities of the agent require additional setup or are only needed in more complex environments. These include: - 2014-09-29T23:59:33.460470Z|vars|:MANUAL v5=1 v6=2 +- connecting multiple devices to a single agent, +- securing communication through TLS, +- ingesting MQTT-based data sources, +- applying custom logic using Ruby, +- enabling optional services like asset ingestion, data transforms, or extended namespaces. -This will remove all the old values from the set and set the current set. Values will accumulate when addition pairs are given as in: +The following sections explain when and why you would use these features, along with short examples. - 2014-09-29T23:59:33.460470Z|vars|v8=1 v9=2 v5=10 +## Multiple Adapters +The agent supports gathering data from multiple devices at the same time. Each device communicates using its own adapter defined by a host and port. -This will add values for v8 and v9 and update the value for v5 to 10. If the values are duplcated they will be removed from the stream unless a `resetTriggered` value is given. +Use this when: - 2014-09-29T23:59:33.460470Z|vars|v8=1 v9=2 v5=0 +- you want one agent instance to serve multiple machines, +- you want to reduce infrastructure overhead, +- machines are located on the same subnet or facility network. -Will be detected as a duplicate with respect to the previous values and will be removed. If a partial update is given and the other values are duplicates, then will be stripped: +Example: +``` +Adapters { + Mill1 { Host = 10.0.0.10 Port = 7878 } + Lathe1 { Host = 10.0.0.20 Port = 7879 } + Router { Host = router.local Port = 9000 } +} +``` - 2014-09-29T23:59:33.460470Z|vars|v8=1 v9=3 v5=10 +Each entry represents one physical machine producing SHDR output. -Will be effectively the same as specifying: +--- - 2014-09-29T23:59:33.460470Z|vars|v9=2 +## HTTP & TLS +HTTP is the standard protocol for MTConnect clients to retrieve data. +TLS (HTTPS) is optional but recommended when: -And the streams will only have the one value when a sample is request is made at that point in the stream. +- data crosses network boundaries, +- sensitive operation data must be encrypted, +- compliance or security policies require secure transport. -When the `discrete` flag is set to `true` in the data item, all change tracking is ignored and each set is treated as if it is new. +TLS enables the agent to serve MTConnect data securely using certificates. -One can quote values using the following methods: `""`, `''`, and `{}`. Spaces and other characters can be included in the text and the matching character can be escaped with a `\` if it is required as follows: `"hello \"there\""` will yield the value: `hellow "there"`. +Example configuration: +``` +TlsDefaults { + PrivateKeyFile = server.key + CertificateFile = server.crt + VerifyClientCertificate = true +} +``` -### `TABLE` Representation ### +After configuration, the agent serves encrypted data over `https://`. -A `TABLE` representation is similar to the `DATA_SET` that has a key value pair as the value. Using the encoding mentioned above, the following representations are allowed for tables: +--- - |wpo|G53.1={X=1.0 Y=2.0 Z=3.0 s='string with space'} G53.2={X=4.0 Y=5.0 Z=6.0} G53.3={X=7.0 Y=8.0 Z=9 U=10.0} +## MQTT +The agent supports MQTT ingestion for devices or sensors that publish data to a broker instead of using SHDR. -Using the quoting conventions explained in the previous section, the inner content can contain quoted text and exscaped values. The value is interpreted as a key/value pair in the same way as the `DATA_SET`. A table can be thought of as a data set of data sets. +Use MQTT when: -All the reset rules of data set apply to tables and the values are treated as a unit. +- working with IoT sensors, gateways, or PLCs, +- devices already publish metrics to an MQTT broker, +- you need a lightweight protocol for high-frequency data. -## Assets +The agent requires the broker address, topics, and connection details: +``` +Mqtt2Service { + Host = tcp://broker:1883 + Topics = factory/mtconnect/# + ClientId = agent01 +} +``` -Assets are associated with a device but do not have a data item they are mapping to. They therefore get the special data item name `@ASSET@`. Assets can be sent either on one line or multiple lines depending on the adapter requirements. The single line form is as follows: +Additional message mapping rules may be needed depending on your topic structure and payload format. - 2012-02-21T23:59:33.460470Z|@ASSET@|KSSP300R.1|CuttingTool|... +--- -This form updates the asset id KSSP300R.1 for a cutting tool with the text at the end. For multiline assets, use the keyword `--multiline--` with a following unique string as follows: +## Ruby Extensions +Ruby extensions allow you to customize how data is processed by the agent before it is exposed to clients. These scripts can: - 2012-02-21T23:59:33.460470Z|@ASSET@|KSSP300R.1|CuttingTool|--multiline--0FED07ACED - - ... - - --multiline--0FED07ACED +- normalize vendor-specific data formats, +- apply mathematical transformations (e.g., unit conversions), +- filter or enrich incoming data, +- modify assets or samples before buffering, +- implement custom logic not present in the MTConnect standard. -The terminal text must appear on the first position after the last line of text. The adapter can also remove assets (1.3) by sending a @REMOVE_ASSET@ with an asset id: +The agent loads Ruby scripts from a directory: +``` +RubyScriptPath = scripts +``` - 2012-02-21T23:59:33.460470Z|@REMOVE_ASSET@|KSSP300R.1 +You can then write Ruby code that hooks into processing events. -Or all assets can be removed in one shot for a certain asset type: +## SHDR (Serial Hierarchical Data Representation) - 2012-02-21T23:59:33.460470Z|@REMOVE_ALL_ASSETS@|CuttingTool +### What SHDR Is +SHDR is the original MTConnect-defined line-based protocol for transmitting machine state and data items. It streams information as timestamped key-value pairs over a plain TCP connection. It is simple, deterministic, and extremely reliable — which is why it continues to be the standard for CNC machine adapters. -Partial updates to assets is also possible by using the @UPDATE_ASSET@ key, but this will only work for cutting tools. The asset id needs to be given and then one of the properties or measurements with the new value for that entity. For example to update the overall tool length and the overall diameter max, you would provide the following: +Example SHDR line: +``` +2025-05-01T10:24:31.123Z|Xpos|12.345 +``` +### Why SHDR Exists - 2012-02-21T23:59:33.460470Z|@UPDATE_ASSET@|KSSP300R.1|OverallToolLength|323.64|CuttingDiameterMax|76.211 +- CNC controls and legacy equipment often cannot speak MQTT/JSON directly. +- SHDR provides a low-overhead, real-time stream that adapters can implement with minimal dependencies. +- It was intentionally created by MTConnect to ensure interoperability across machine vendors. -## MQTT JSON Ingress Protocol Version 2.0 +### When to Use SHDR +Choose SHDR if: +- you are connecting to Fanuc, Mazak, Okuma, Haas, etc. +- you need deterministic, line-based real-time updates +- you are using existing MTConnect adapters shipped with machine vendors -In general the data format is {"timestamp": "YYYY-MM-DDThh:mm:ssZ","dataItemId":"value", "dataItemId":{"key1":"value1", ..., "keyn":"valuen}} +### Alternatives -**NOTE**: See the standard for the complete description of the fields for the data item representations below. +| Protocol | Best for | Notes | +|-------|--------|----------| +| SHDR | CNC & legacy equipment | Most common MTConnect adapter input | +| MQTT | IoT sensors, PLC networks, mixed devices | Requires broker & topic mapping | +| JSON | Modern controllers or software-only adapters | Human-readable, flexible format | -A simple set of events and samples will look something like this: +### Documentation & Reference -```json -{ - "timestamp": "2023-11-06T12:12:44Z", //Time Stamp - "tempId": 22.6, //Temperature - "positionId": 1002.345, //X axis position - "executionId": "ACTIVE" //Execution state -} -``` +- MTConnect Adapters: [https://github.com/mtconnect/adapter](https://github.com/mtconnect/adapter) -A `CONDITION` requires the key to be the dataItemId and requires the 6 fields as shown in the example below - -```json -{ - "timestamp": "2023-11-06T12:12:44Z", - "dataItemId": { - "level": "fault", - "conditionId":"ac324", - "nativeSeverity": "1000", - "qualifier": "HIGH", - "nativeCode": "ABC", - "message": "something went wrong" - } -} -``` -A `MESSAGE` requires the key to be the dataItemId and requires the nativeCode field as shown in the example below - -```json -{ - "timestamp": "2023-11-06T12:12:44Z", - "messsageId": { - "nativeCode": "ABC", - "message": "something went wrong" - } -} -``` +### Why SHDR Is a One-Off +Unlike MQTT or JSON, SHDR is not a general IoT messaging protocol. It only exists in MTConnect ecosystems and is purpose-built for CNC-level real-time telemetry. It remains widely deployed but many modern environments now run blended architectures: -The `TimeSeries` `REPRESENTATION` requires the key to be the dataItemId and requires 2 fields "count" and "values" and 1 to n comma delimited values. ->**NOTE**: The "frequency" field is optional. - -```json -{ - "timestamp": "2023-11-06T12:12:44Z", - "timeSeries1": { - "count": 10, - "frequency": 100, - "values": [1,2,3,4,5,6,7,8,9,10] - } -} ``` -The `DataSet` `REPRESENTATION` requires the the dataItemId as the key and the "values" field. It may also have the optional "resetTriggered" field. - -```json -{ -{ - "timestamp": "2023-11-09T11:20:00Z", - "dataSetId": { - "key1": 123, - "key2": 456, - "key3": 789 - } -} +CNC → SHDR → Agent +IoT Sensor → MQTT → Agent +Software Device → JSON → Agent ``` - -Example with the optional "resetTriggered" filed: - -```json -{ - "timestamp": "2023-11-09T11:20:00Z", - "cncregisterset1": { - "resetTriggered": "NEW", - "value": {"r1":"v1", "r2":"v2", "r3":"v3" } - } -} +--- + +# 📤 Sample Outputs +Understanding what the agent returns helps developers build clients effectively. + +### /probe +Shows device structure: +```xml + + + ... + + ``` -The `Table` `REPRESENTATION` requires the the dataItemId as the key and the "values" field. It may also have the optional "resetTriggered" field. - -```json - -{ - "timestamp":"2023-11-06T12:12:44Z", - "tableId":{ - "row1":{ - "cell1":"Some Text", - "cell2":3243 - }, - "row2": { - "cell1":"Some Other Text", - "cell2":243 - } - } -} +### /current +Shows the latest sample for every data item. +```xml +12.345 ``` -Example with the optional resetTriggered field: - -```json -{ - "timestamp": "2023-11-09T11:20:00Z", - "a1": { - "resetTriggered": "NEW", - "value": { - "r1": { - "k1": 123.45, - "k3": 6789 - }, - "r2": null - } - } -} +### /sample +Shows buffered time series data. +```xml +3500 ``` -## Adapter Commands -There are a number of commands that can be sent as part of the adapter stream over the SHDR port connection. These change some dynamic elements of the device information, the interpretation of the data, or the associated default device. Commands are given on a single line starting with an asterisk `* ` as the first character of the line and followed by a : . They are as follows: - -* Specify the Adapter Software Version the adapter supports: - - `* adapterVersion: ` - -* Set the calibration in the device component of the associated device: - - `* calibration: XXX` - -* Tell the agent that the data coming from this adapter requires conversion: - - `* conversionRequired: ` - -* Set the description in the device header of the associated device: - - `* description: XXX` - -* Specify the default device for this adapter. The device can be specified as either the device name or UUID: - - `* device: ` - -* Tell the agent to load a new device XML model: - - `* devicemodel: ` - -* Set the manufacturer in the device header of the associated device: - - `* manufacturer: XXX` - -* Specify the MTConnect Version the adapter supports: - - `* mtconnectVersion: ` - -* Set the nativeName in the device component of the associated device: - - `* nativeName: XXX` - -* Tell the agent that the data coming from this adapter would like real-time priority: - - `* realTime: ` - -* Tell the agent that the data coming from this adapter is specified in relative time: - - `* relativeTime: ` - -* Set the serialNumber in the device header of the associated device: - - `* serialNumber: XXX` - -* Specify the version of the SHDR protocol delivered by the adapter. See `ShdrVersion` above: - - `* shdrVersion: ` - -* Set the station in the device header of the associated device: - - `* station: XXX` - -* Set the uuid in the device header of the associated device if preserveUuid = false: - - `* uuid: XXX` - -Any other command will be logged as a warning. - -## Heartbeat Protocol - -The agent and the adapter have a heartbeat that makes sure each is responsive to properly handle disconnects in a timely manner. The Heartbeat frequency is set by the adapter and honored by the agent. When the agent connects to the adapter, it first sends a `* PING` and then expects the response `* PONG ` where `` is specified in milliseconds. So if the following communications are given: - -Agent: - - * PING - -Adapter: - - * PONG 10000 - -This indicates that the adapter is expecting a `PING` every 10 seconds and if there is no `PING`, in 2x the frequency, then the adapter should close the connection. At the same time, if the agent does not receive a `PONG` within 2x frequency, then it will close the connection. If no `PONG` response is received, the agent assumes the adapter is incapable of participating in heartbeat protocol and uses the legacy time specified above. - -Just as with the SHDR protocol, these messages must end with an LF (ASCII 10) or CR-LF (ASCII 15 followed by ASCII 10). - -HTTP PUT/POST Method of Uploading Data ------ - -There are two configuration settings mentioned above: `AllowPut` and `AllowPutFrom`. `AllowPut` alone will allow any process to use `HTTP` `POST` or `PUT` to send data to the agent and modify values. To restrict this to a limited number of -machines, you can list the IP Addresses that are allowed to `POST` data to the agent. - -An example would be: - - AllowPut = yes - AllowPutFrom = 192.168.1.72, 192.168.1.73 - -This will allow the two machines to post data to the MTConnect agent. The data can be either data item values or assets. The primary use of this capability is uploading assets from a process or even the command line using utilities like curl. I'll be using curl for these examples. - -For example, with curl you can use the -d option to send data to the server. The data will be in standard form data format, so all you need to do is to pass the `=` to set the values, as follows: - - curl -d 'avail=AVAILABLE&program_1=XXX' 'http://localhost:5000/ExampleDevice' +These outputs comply with the MTConnect schema and can be validated using standard tools. -By specifying the device at the end of the URL, you tell the agent which device to use for the POST. This will set the availability tag to AVAILABLE and the program to XXX: +--- - AVAILABLE - ... - XXX +# 📁 Directory Structure -The full raw data being passed over looks like this: - - => Send header, 161 bytes (0xa1) - 0000: POST /ExampleDevice HTTP/1.1 - 001e: User-Agent: curl/7.37.1 - 0037: Host: localhost:5000 - 004d: Accept: */* - 005a: Content-Length: 29 - 006e: Content-Type: application/x-www-form-urlencoded - 009f: - => Send data, 29 bytes (0x1d) - 0000: avail=AVAILABLE&program_1=XXX - == Info: upload completely sent off: 29 out of 29 bytes - == Info: HTTP 1.0, assume close after body - <= Recv header, 17 bytes (0x11) - 0000: HTTP/1.0 200 OK - <= Recv header, 20 bytes (0x14) - 0000: Content-Length: 10 - <= Recv header, 24 bytes (0x18) - 0000: Content-Type: text/xml - <= Recv header, 2 bytes (0x2) - 0000: - <= Recv data, 10 bytes (0xa) - 0000: - >== Info: Closing connection 0 - -This is using the --trace - to dump the internal data. The response will be a simple `` or ``. - -Any data item can be set in this fashion. Similarly conditions are set using the following syntax: - - curl -d 'system=fault|XXX|1|LOW|Feeling%20low' 'http://localhost:5000/ExampleDevice' - -One thing to note, the data and values are URL encoded, so the space needs to be encoded as a %20 to appear correctly. - - Feeling Low - -Assets are posted in a similar fashion. The data will be taken from a file containing the XML for the content. The syntax is very similar to the other requests: - - curl -d @B732A08500HP.xml 'http://localhost:5000/asset/B732A08500HP.1?device=ExampleDevice&type=CuttingTool' - -The @... uses the named file to pass the data and the URL must contain the asset id and the device name as well as the asset type. If the type is CuttingTool or CuttingToolArchetype, the data will be parsed and corrected if properties are out of order as with the adapter. If the device is not specified and there are more than one device in this adapter, it will cause an error to be returned. - -Programmatically, send the data as the body of the POST or PUT request as follows. If we look at the raw data, you will see the data is sent over verbatim as follows: - - => Send header, 230 bytes (0xe6) - 0000: POST /asset/B732A08500HP.1?device=ExampleDevice&type=CuttingTool - 0040: HTTP/1.1 - 004b: User-Agent: curl/7.37.1 - 0064: Host: localhost:5000 - 007a: Accept: */* - 0087: Content-Length: 2057 - 009d: Content-Type: application/x-www-form-urlencoded - 00ce: Expect: 100-continue - 00e4: - == Info: Done waiting for 100-continue - => Send data, 2057 bytes (0x809) - 0000: (file data sent here, see below...) - == Info: HTTP 1.0, assume close after body - <= Recv header, 17 bytes (0x11) - 0000: HTTP/1.0 200 OK - <= Recv header, 20 bytes (0x14) - 0000: Content-Length: 10 - <= Recv header, 24 bytes (0x18) - 0000: Content-Type: text/xml - <= Recv header, 2 bytes (0x2) - 0000: - <= Recv data, 10 bytes (0xa) - 0000: - == Info: Closing connection 0 - -The file that was included looks like this: - - - - Step Drill KMT, B732A08500HP Grade KC7315 - Adapter KMT CV50BHPVTT12M375 - - - NEW - 5893 - 2.5 - CV50 Taper - - 31.8 - 120.825 - 158.965 - 98.425 - 257.35 - - - - - 8.513 - 89.8551 - 157.259 - 9 - 135.1540 - - - - - 11.999 - 125.500 - 9 - - - - - - -An example in ruby is as follows: - - > require 'net/http' - => true - > h = Net::HTTP.new('localhost', 5000) - => # - > r = h.post('/asset/B732A08500HP.1?type=CuttingTool&device=ExampleDevice', File.read('B732A08500HP.xml')) - => # - > r.body - => "" - -# Building the agent - -## Overview - -The agent build is dependent on the following utilities: - -* C++ Compiler compliant with C++ 17 -* git is optional but suggested to download source and update when changes occur -* cmake for build generator and testing -* python 3 and pip to support conan for dependency and package management -* ruby and rake for mruby to support building the embedded scripting engine [not required if -o with_ruby=False] - -## Conan MTConnect Options (set using `-o