From 0cffec0f64510c1be3cdb928aadf34de3f2cee3b Mon Sep 17 00:00:00 2001 From: dwickelhaus Date: Mon, 28 Aug 2023 21:17:35 -0400 Subject: [PATCH 01/12] added condition_id to the CONDITION SHDR format and example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 README.md diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 00a0558d..4ac5310a --- a/README.md +++ b/README.md @@ -937,13 +937,13 @@ If the value itself contains a pipe character `|` the pipe must be escaped using 2009-06-15T00:00:00.000000|description|"Text with \| (pipe) character." -Conditions require six (6) fields as follows: +Conditions require seven (7) fields as follows: - |||||| + ||||||| For a complete description of these fields, see the standard. An example line will look like this: - 2014-09-29T23:59:33.460470Z|htemp|WARNING|HTEMP|1|HIGH|Oil Temperature High + 2014-09-29T23:59:33.460470Z|htemp|WARNING|HTEMP-1-HIGH|HTEMP|1|HIGH|Oil Temperature High The next special format is the Message. There is one additional field, native_code, which needs to be included: From c0db9e7b084a974a3e7279715abf812883a4e623 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 3 Oct 2023 22:02:48 +0200 Subject: [PATCH 02/12] Added support for conditionId in version 2.3 and native code field in the format : --- src/mtconnect/agent.hpp | 5 ++ src/mtconnect/observation/observation.cpp | 1 + src/mtconnect/observation/observation.hpp | 1 + src/mtconnect/pipeline/pipeline_contract.hpp | 3 + src/mtconnect/pipeline/shdr_token_mapper.cpp | 46 +++++++++- src/mtconnect/utilities.hpp | 1 + test_package/agent_adapter_test.cpp | 1 + test_package/data_item_mapping_test.cpp | 84 ++++++++++++++++++- test_package/duplicate_filter_test.cpp | 1 + test_package/embedded_ruby_test.cpp | 1 + test_package/mtconnect_xml_transform_test.cpp | 1 + test_package/period_filter_test.cpp | 1 + test_package/response_document_test.cpp | 1 + test_package/topic_mapping_test.cpp | 1 + 14 files changed, 143 insertions(+), 5 deletions(-) diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp index a7e42865..6d206366 100644 --- a/src/mtconnect/agent.hpp +++ b/src/mtconnect/agent.hpp @@ -209,6 +209,10 @@ namespace mtconnect { /// @brief Get the MTConnect schema version the agent is supporting /// @return The MTConnect schema version as a string const auto &getSchemaVersion() const { return m_schemaVersion; } + + /// @brief Get the integer schema version based on configuration. + /// @returns the schema version as an integer [major * 100 + minor] as a 32bit integer. + const auto getIntSchemaVersion() const { return m_intSchemaVersion; } /// @brief Find a device by name /// @param[in] name The name of the device to find @@ -575,6 +579,7 @@ namespace mtconnect { fun(ldi); } } + int32_t getSchemaVersion() const override { return m_agent->getIntSchemaVersion(); } void deliverObservation(observation::ObservationPtr obs) override { m_agent->receiveObservation(obs); diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index 4cb0804e..e1a920df 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -341,6 +341,7 @@ namespace mtconnect { }); factory->addRequirements(Requirements {{"type", USTRING, true}, {"nativeCode", false}, + {"conditionId", false}, {"nativeSeverity", false}, {"qualifier", USTRING, false}, {"statistic", USTRING, false}, diff --git a/src/mtconnect/observation/observation.hpp b/src/mtconnect/observation/observation.hpp index 7548b3dd..36cb967d 100644 --- a/src/mtconnect/observation/observation.hpp +++ b/src/mtconnect/observation/observation.hpp @@ -280,6 +280,7 @@ namespace mtconnect::observation { m_level = NORMAL; m_code.clear(); m_properties.erase("nativeCode"); + m_properties.erase("conditionId"); m_properties.erase("nativeSeverity"); m_properties.erase("qualifier"); m_properties.erase("statistic"); diff --git a/src/mtconnect/pipeline/pipeline_contract.hpp b/src/mtconnect/pipeline/pipeline_contract.hpp index 977fb682..4de67eb1 100644 --- a/src/mtconnect/pipeline/pipeline_contract.hpp +++ b/src/mtconnect/pipeline/pipeline_contract.hpp @@ -73,6 +73,9 @@ namespace mtconnect { /// @param[in] name name or id of the data item /// @return shared pointer to the data item if found virtual DataItemPtr findDataItem(const std::string &device, const std::string &name) = 0; + /// @brief get the current schema version as an integer + /// @returns the schema version as an integer [major * 100 + minor] as a 32bit integer. + virtual int32_t getSchemaVersion() const = 0; /// @brief iterate through all the data items calling `fun` for each /// @param[in] fun The function or lambda to call virtual void eachDataItem(EachDataItem fun) = 0; diff --git a/src/mtconnect/pipeline/shdr_token_mapper.cpp b/src/mtconnect/pipeline/shdr_token_mapper.cpp index 31996a2e..644763cc 100644 --- a/src/mtconnect/pipeline/shdr_token_mapper.cpp +++ b/src/mtconnect/pipeline/shdr_token_mapper.cpp @@ -37,6 +37,20 @@ namespace mtconnect { [](const char a, const char b) { return toupper(a) == b; }); } + inline static std::pair, std::optional> splitPair( + const std::string &key) + { + string_view sv(key.c_str()); + auto c = sv.find(':'); + + if (c == 0) + return { nullopt, sv.substr(c + 1, string::npos) }; + else if (c != string_view::npos) + return { sv.substr(0, c), sv.substr(c + 1, string::npos) }; + else + return { sv, nullopt }; + } + inline static std::pair> splitKey( const std::string &key) { @@ -140,7 +154,8 @@ namespace mtconnect { inline ObservationPtr zipProperties(const DataItemPtr dataItem, const Timestamp ×tamp, const entity::Requirements &reqs, TokenList::const_iterator &token, - const TokenList::const_iterator &end, ErrorList &errors) + const TokenList::const_iterator &end, ErrorList &errors, + int32_t schemaVersion) { NAMED_SCOPE("zipProperties"); Properties props; @@ -173,6 +188,31 @@ namespace mtconnect { << "': " << *token << " - " << e.what(); } } + + if (dataItem->isCondition() && schemaVersion >= SCHEMA_VERSION(2, 3)) + { + auto nc = props.find("nativeCode"); + if (nc != props.end()) + { + auto code = get(nc->second); + auto values = splitPair(code); + if (values.first && values.second) + { + props["nativeCode"] = string(*values.first); + props["conditionId"] = string(*values.second); + } + else if (values.first) + { + props["nativeCode"] = string(*values.first); + props["conditionId"] = string(*values.first); + } + else if (values.second) + { + props["conditionId"] = string(*values.second); + props.erase("nativeCode"); + } + } + } return Observation::make(dataItem, props, timestamp, errors); } @@ -190,7 +230,7 @@ namespace mtconnect { if (dataItemIt == m_dataItemMap.end() || !(dataItem = dataItemIt->second.lock())) { auto dataItemKey = splitKey(key); - string device = dataItemKey.second.value_or(m_defaultDevice.value_or("")); + string device { dataItemKey.second.value_or(m_defaultDevice.value_or("")) }; dataItem = m_contract->findDataItem(device, dataItemKey.first); if (dataItem == nullptr) @@ -250,7 +290,7 @@ namespace mtconnect { if (reqs != nullptr) { - auto obs = zipProperties(dataItem, timestamp, *reqs, token, end, errors); + auto obs = zipProperties(dataItem, timestamp, *reqs, token, end, errors, m_contract->getSchemaVersion()); if (dataItem->getConstantValue()) return nullptr; if (obs && source) diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index f6ddbc07..4fee077c 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -416,6 +416,7 @@ namespace mtconnect { using Microseconds = std::chrono::microseconds; using Seconds = std::chrono::seconds; using Timestamp = std::chrono::time_point; + using Timestamp = std::chrono::time_point; using StringList = std::list; /// @name Configuration related methods diff --git a/test_package/agent_adapter_test.cpp b/test_package/agent_adapter_test.cpp index d814470b..2942befa 100644 --- a/test_package/agent_adapter_test.cpp +++ b/test_package/agent_adapter_test.cpp @@ -78,6 +78,7 @@ struct MockPipelineContract : public PipelineContract } void deliverAsset(AssetPtr) override {} void deliverDevices(std::list d) override { m_receivedDevice = d.front(); } + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverAssetCommand(entity::EntityPtr) override {} void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &dev, bool flag) override {} diff --git a/test_package/data_item_mapping_test.cpp b/test_package/data_item_mapping_test.cpp index edab0494..d030f882 100644 --- a/test_package/data_item_mapping_test.cpp +++ b/test_package/data_item_mapping_test.cpp @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) class MockPipelineContract : public PipelineContract { public: - MockPipelineContract(std::map &items) : m_dataItems(items) {} + MockPipelineContract(std::map &items, int32_t schemaVersion) : m_dataItems(items), m_schemaVersion(schemaVersion) {} DevicePtr findDevice(const std::string &) override { return nullptr; } DataItemPtr findDataItem(const std::string &device, const std::string &name) override { @@ -54,6 +54,7 @@ class MockPipelineContract : public PipelineContract void deliverObservation(observation::ObservationPtr obs) override {} void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} + int32_t getSchemaVersion() const override { return m_schemaVersion; } void deliverAssetCommand(entity::EntityPtr) override {} void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} @@ -61,6 +62,7 @@ class MockPipelineContract : public PipelineContract const ObservationPtr checkDuplicate(const ObservationPtr &obs) const override { return obs; } std::map &m_dataItems; + int32_t m_schemaVersion; }; class DataItemMappingTest : public testing::Test @@ -69,7 +71,8 @@ class DataItemMappingTest : public testing::Test void SetUp() override { m_context = make_shared(); - m_context->m_contract = make_unique(m_dataItems); + m_context->m_contract = make_unique(m_dataItems, + SCHEMA_VERSION(2, 0)); m_mapper = make_shared(m_context, "", 2); m_mapper->bind(make_shared(TypeGuard(RUN))); } @@ -309,6 +312,7 @@ TEST_F(DataItemMappingTest, ConditionNormal) ASSERT_TRUE(di->isCondition()); ASSERT_TRUE(cond->hasProperty("VALUE")); ASSERT_FALSE(cond->hasProperty("nativeCode")); + ASSERT_FALSE(cond->hasProperty("conditionId")); ASSERT_FALSE(cond->hasProperty("qualifier")); ASSERT_EQ("Normal", cond->getName()); } @@ -532,3 +536,79 @@ TEST_F(DataItemMappingTest, continue_after_conversion_error) ASSERT_TRUE(prog->isEvent()); ASSERT_EQ("program", program->getValue()); } + +TEST_F(DataItemMappingTest, version_23_condition_behavior_with_native_code) +{ + auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); + auto *context = dynamic_cast(m_context->m_contract.get()); + context->m_schemaVersion = SCHEMA_VERSION(2, 3); + // ||||| + + auto ts = makeTimestamped({"a", "fault", "A123", "bad", "HIGH", "Something Bad"}); + auto observations = (*m_mapper)(ts); + auto oblist = observations->getValue(); + ASSERT_EQ(1, oblist.size()); + + auto cond = dynamic_pointer_cast(oblist.front()); + ASSERT_TRUE(cond); + + ASSERT_EQ(di, cond->getDataItem()); + ASSERT_TRUE(di->isCondition()); + ASSERT_EQ("Something Bad", cond->getValue()); + ASSERT_EQ("A123", cond->get("nativeCode")); + ASSERT_EQ("A123", cond->get("conditionId")); + ASSERT_EQ("HIGH", cond->get("qualifier")); + ASSERT_EQ("Fault", cond->getName()); + +} + +TEST_F(DataItemMappingTest, version_23_condition_behavior_with_condition_id) +{ + auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); + auto *context = dynamic_cast(m_context->m_contract.get()); + context->m_schemaVersion = SCHEMA_VERSION(2, 3); + // ||||| + + auto ts = makeTimestamped({"a", "fault", "A123:B456", "bad", "HIGH", "Something Bad"}); + auto observations = (*m_mapper)(ts); + auto oblist = observations->getValue(); + ASSERT_EQ(1, oblist.size()); + + auto cond = dynamic_pointer_cast(oblist.front()); + ASSERT_TRUE(cond); + + ASSERT_EQ(di, cond->getDataItem()); + ASSERT_TRUE(di->isCondition()); + ASSERT_EQ("Something Bad", cond->getValue()); + ASSERT_EQ("A123", cond->get("nativeCode")); + ASSERT_EQ("B456", cond->get("conditionId")); + ASSERT_EQ("HIGH", cond->get("qualifier")); + ASSERT_EQ("Fault", cond->getName()); + +} + +TEST_F(DataItemMappingTest, version_23_condition_behavior_with_only_condition_id) +{ + auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); + auto *context = dynamic_cast(m_context->m_contract.get()); + context->m_schemaVersion = SCHEMA_VERSION(2, 3); + // ||||| + + auto ts = makeTimestamped({"a", "fault", ":B456", "bad", "HIGH", "Something Bad"}); + auto observations = (*m_mapper)(ts); + auto oblist = observations->getValue(); + ASSERT_EQ(1, oblist.size()); + + auto cond = dynamic_pointer_cast(oblist.front()); + ASSERT_TRUE(cond); + + ASSERT_EQ(di, cond->getDataItem()); + ASSERT_TRUE(di->isCondition()); + ASSERT_EQ("Something Bad", cond->getValue()); + ASSERT_FALSE(cond->hasProperty("nativeCode")); + ASSERT_EQ("B456", cond->get("conditionId")); + ASSERT_EQ("HIGH", cond->get("qualifier")); + ASSERT_EQ("Fault", cond->getName()); + +} + diff --git a/test_package/duplicate_filter_test.cpp b/test_package/duplicate_filter_test.cpp index 9c40401e..a9b1de3f 100644 --- a/test_package/duplicate_filter_test.cpp +++ b/test_package/duplicate_filter_test.cpp @@ -65,6 +65,7 @@ class MockPipelineContract : public PipelineContract void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} void deliverAssetCommand(entity::EntityPtr) override {} + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} diff --git a/test_package/embedded_ruby_test.cpp b/test_package/embedded_ruby_test.cpp index d5138fe8..b12e36b5 100644 --- a/test_package/embedded_ruby_test.cpp +++ b/test_package/embedded_ruby_test.cpp @@ -90,6 +90,7 @@ namespace { void deliverAsset(AssetPtr a) override { m_asset = a; } void deliverDevices(std::list) override {} void deliverAssetCommand(entity::EntityPtr c) override { m_command = c; } + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverCommand(entity::EntityPtr c) override { m_command = c; } void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} diff --git a/test_package/mtconnect_xml_transform_test.cpp b/test_package/mtconnect_xml_transform_test.cpp index b2c59d16..cb2b345e 100644 --- a/test_package/mtconnect_xml_transform_test.cpp +++ b/test_package/mtconnect_xml_transform_test.cpp @@ -51,6 +51,7 @@ class MockPipelineContract : public PipelineContract return m_device->getDeviceDataItem(name); } void eachDataItem(EachDataItem fun) override {} + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverObservation(observation::ObservationPtr obs) override {} void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} diff --git a/test_package/period_filter_test.cpp b/test_package/period_filter_test.cpp index d47b31fd..839792ce 100644 --- a/test_package/period_filter_test.cpp +++ b/test_package/period_filter_test.cpp @@ -65,6 +65,7 @@ struct MockPipelineContract : public PipelineContract void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} void deliverAssetCommand(entity::EntityPtr) override {} + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} diff --git a/test_package/response_document_test.cpp b/test_package/response_document_test.cpp index 45508028..0ae7316b 100644 --- a/test_package/response_document_test.cpp +++ b/test_package/response_document_test.cpp @@ -57,6 +57,7 @@ class MockPipelineContract : public PipelineContract void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} void deliverAssetCommand(entity::EntityPtr) override {} + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} diff --git a/test_package/topic_mapping_test.cpp b/test_package/topic_mapping_test.cpp index 050ae05f..71a53f0b 100644 --- a/test_package/topic_mapping_test.cpp +++ b/test_package/topic_mapping_test.cpp @@ -57,6 +57,7 @@ class MockPipelineContract : public PipelineContract void deliverAsset(AssetPtr) override {} void deliverDevices(std::list) override {} void deliverAssetCommand(entity::EntityPtr) override {} + int32_t getSchemaVersion() const override { return IntDefaultSchemaVersion(); } void deliverCommand(entity::EntityPtr) override {} void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} From 2d4e473913365d1e124727f2b3b786e6ad204327 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Wed, 4 Oct 2023 12:10:45 +0200 Subject: [PATCH 03/12] default condition code to condition id when creating condition observation added test to validate conditionId will be used for condition chaining in checkpoint --- src/mtconnect/agent.hpp | 2 +- src/mtconnect/configuration/agent_config.cpp | 14 +- src/mtconnect/configuration/agent_config.hpp | 8 +- src/mtconnect/observation/change_observer.cpp | 5 +- src/mtconnect/observation/change_observer.hpp | 14 +- src/mtconnect/observation/observation.cpp | 11 +- src/mtconnect/pipeline/period_filter.hpp | 20 +-- src/mtconnect/pipeline/shdr_token_mapper.cpp | 19 +-- test_package/change_observer_test.cpp | 45 ++++--- test_package/checkpoint_test.cpp | 123 ++++++++++++++++++ test_package/config_test.cpp | 4 +- test_package/data_item_mapping_test.cpp | 17 +-- 12 files changed, 207 insertions(+), 75 deletions(-) diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp index 6d206366..554f239b 100644 --- a/src/mtconnect/agent.hpp +++ b/src/mtconnect/agent.hpp @@ -209,7 +209,7 @@ namespace mtconnect { /// @brief Get the MTConnect schema version the agent is supporting /// @return The MTConnect schema version as a string const auto &getSchemaVersion() const { return m_schemaVersion; } - + /// @brief Get the integer schema version based on configuration. /// @returns the schema version as an integer [major * 100 + minor] as a 32bit integer. const auto getIntSchemaVersion() const { return m_intSchemaVersion; } diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 26cf62b4..02e7c5b5 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -17,8 +17,6 @@ #include "agent_config.hpp" -#include -#include #include #include #include @@ -33,6 +31,8 @@ #include #include #include +#include +#include #include "mtconnect/config.hpp" @@ -180,7 +180,7 @@ namespace mtconnect::configuration { m_configFile = fs::canonical(*path); addPathFront(m_configPaths, m_configFile.parent_path()); addPathBack(m_dataPaths, m_configFile.parent_path()); - + ifstream file(m_configFile.c_str()); std::stringstream buffer; buffer << file.rdbuf(); @@ -697,14 +697,13 @@ namespace mtconnect::configuration { ExpandValues(values, config); } - void AgentConfiguration::loadConfig(const std::string &text, - FileFormat fmt) + void AgentConfiguration::loadConfig(const std::string &text, FileFormat fmt) { NAMED_SCOPE("AgentConfiguration::loadConfig"); // Now get our configuration boost::property_tree::ptree config; - + try { switch (fmt) @@ -732,7 +731,8 @@ namespace mtconnect::configuration { cerr << "json file error: " << e.what() << " on line " << e.line() << endl; throw e; } - catch (std::exception e) { + catch (std::exception e) + { cerr << "could not load config file: " << e.what() << endl; throw e; } diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 5b00f3ee..b2c12ed7 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -70,13 +70,14 @@ namespace mtconnect { class AGENT_LIB_API AgentConfiguration : public MTConnectService { public: - enum FileFormat { + enum FileFormat + { MTCONNECT, JSON, XML, UNKNOWN }; - + using InitializationFn = void(const boost::property_tree::ptree &, AgentConfiguration &); using InitializationFunction = boost::function; @@ -114,8 +115,7 @@ namespace mtconnect { /// @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); + 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 diff --git a/src/mtconnect/observation/change_observer.cpp b/src/mtconnect/observation/change_observer.cpp index 60ab78ad..639298af 100644 --- a/src/mtconnect/observation/change_observer.cpp +++ b/src/mtconnect/observation/change_observer.cpp @@ -44,7 +44,7 @@ namespace mtconnect::observation { m_signalers.erase(newEndPos); return true; } - + void ChangeObserver::handler(boost::system::error_code ec) { boost::asio::dispatch(m_strand, boost::bind(m_handler, ec)); @@ -106,8 +106,7 @@ namespace mtconnect::observation { m_strand(strand), m_observer(strand), m_buffer(buffer) - { - } + {} void AsyncObserver::observe(const std::optional &from, Resolver resolver) { diff --git a/src/mtconnect/observation/change_observer.hpp b/src/mtconnect/observation/change_observer.hpp index 67feb250..962baf3b 100644 --- a/src/mtconnect/observation/change_observer.hpp +++ b/src/mtconnect/observation/change_observer.hpp @@ -46,7 +46,7 @@ namespace mtconnect::observation { {} virtual ~ChangeObserver(); - + /// @brief dispatch handler /// /// this is only necessary becase of issue with windows DLLs @@ -54,7 +54,8 @@ namespace mtconnect::observation { /// @param ec the error code from the callback void handler(boost::system::error_code ec); - /// @brief wait for a signal to occur asynchronously. If it is already signaled, call the callback immediately. + /// @brief wait for a signal to occur asynchronously. If it is already signaled, call the + /// callback immediately. /// @param duration the duration to wait /// @param handler the handler to call back /// @return `true` if successful @@ -79,7 +80,7 @@ namespace mtconnect::observation { } return true; } - + /// @brief wait a period of time where signals will not cancle the timer /// @param duration the duration to wait /// @param handler the handler to call back @@ -95,7 +96,6 @@ namespace mtconnect::observation { return true; } - /// @brief single all waiting observers if this sequence number is greater than the last /// /// also cancel the timer @@ -111,7 +111,7 @@ namespace mtconnect::observation { { if (m_timer.cancel() == 0) { - //LOG(trace) << "Cannot cancel timer"; + // LOG(trace) << "Cannot cancel timer"; } } } @@ -128,7 +128,7 @@ namespace mtconnect::observation { m_sequence = UINT64_MAX; m_noCancelOnSignal = false; } - + /// @brief handler for the callback boost::function m_handler; @@ -139,7 +139,7 @@ namespace mtconnect::observation { std::list m_signalers; volatile uint64_t m_sequence = UINT64_MAX; - bool m_noCancelOnSignal { false }; + bool m_noCancelOnSignal {false}; protected: friend class ChangeSignaler; diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index e1a920df..a5fce7c5 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -333,9 +333,16 @@ namespace mtconnect { auto cond = make_shared(name, props); if (cond) { - auto code = cond->m_properties.find("nativeCode"); - if (code != cond->m_properties.end()) + if (auto code = cond->m_properties.find("conditionId"); + code != cond->m_properties.end()) + { cond->m_code = std::get(code->second); + } + else if (auto code = cond->m_properties.find("nativeCode"); + code != cond->m_properties.end()) + { + cond->m_code = std::get(code->second); + } } return cond; }); diff --git a/src/mtconnect/pipeline/period_filter.hpp b/src/mtconnect/pipeline/period_filter.hpp index 836faf6b..4d282638 100644 --- a/src/mtconnect/pipeline/period_filter.hpp +++ b/src/mtconnect/pipeline/period_filter.hpp @@ -140,9 +140,10 @@ namespace mtconnect::pipeline { const auto &ts = obs->getTimestamp(); #ifdef DEBUG_PERIOD_FILTER - std::cout << "<<<< Delta for obs at " << format(ts) << " is " << duration_cast(last.m_next - ts).count() << std::endl; + std::cout << "<<<< Delta for obs at " << format(ts) << " is " + << duration_cast(last.m_next - ts).count() << std::endl; #endif - + const auto start = last.m_next - last.m_period; const auto &end = last.m_next; @@ -193,7 +194,7 @@ namespace mtconnect::pipeline { // Similar to the delayed send, the last timestamp is computed as the end // of the previous period. last.m_next += last.m_period; - + #ifdef DEBUG_PERIOD_FILTER std::cout << " last timestamp set to " << format(last.m_next) << std::endl; #endif @@ -241,7 +242,7 @@ namespace mtconnect::pipeline { // Set the timer to expire in the remaining time left in the period given // in last.m_delta last.m_timer.cancel(); - const auto now { system_clock::now() }; + const auto now {system_clock::now()}; const auto delta = last.m_next - now; last.m_timer.expires_after(delta); @@ -282,17 +283,18 @@ namespace mtconnect::pipeline { auto &last = lastIt->second; #ifdef DEBUG_PERIOD_FILTER - std::cout << "sendObservation: last timestamp is " << format(last.m_observation->getTimestamp()) - << " next " << format(last.m_next) << std::endl; + std::cout << "sendObservation: last timestamp is " + << format(last.m_observation->getTimestamp()) << " next " << format(last.m_next) + << std::endl; #endif last.m_observation.swap(obs); - auto now { system_clock::now() }; + auto now {system_clock::now()}; if (now >= last.m_next) { last.m_next += last.m_period; #ifdef DEBUG_PERIOD_FILTER - std::cout << "sendObservation: setting timestamp to " << format(last.m_next) - << " now " << format(now) << std::endl; + std::cout << "sendObservation: setting timestamp to " << format(last.m_next) << " now " + << format(now) << std::endl; #endif } else diff --git a/src/mtconnect/pipeline/shdr_token_mapper.cpp b/src/mtconnect/pipeline/shdr_token_mapper.cpp index 644763cc..97ebbcdd 100644 --- a/src/mtconnect/pipeline/shdr_token_mapper.cpp +++ b/src/mtconnect/pipeline/shdr_token_mapper.cpp @@ -37,18 +37,18 @@ namespace mtconnect { [](const char a, const char b) { return toupper(a) == b; }); } - inline static std::pair, std::optional> splitPair( - const std::string &key) + inline static std::pair, std::optional> + splitPair(const std::string &key) { string_view sv(key.c_str()); auto c = sv.find(':'); - + if (c == 0) - return { nullopt, sv.substr(c + 1, string::npos) }; + return {nullopt, sv.substr(c + 1, string::npos)}; else if (c != string_view::npos) - return { sv.substr(0, c), sv.substr(c + 1, string::npos) }; + return {sv.substr(0, c), sv.substr(c + 1, string::npos)}; else - return { sv, nullopt }; + return {sv, nullopt}; } inline static std::pair> splitKey( @@ -188,7 +188,7 @@ namespace mtconnect { << "': " << *token << " - " << e.what(); } } - + if (dataItem->isCondition() && schemaVersion >= SCHEMA_VERSION(2, 3)) { auto nc = props.find("nativeCode"); @@ -230,7 +230,7 @@ namespace mtconnect { if (dataItemIt == m_dataItemMap.end() || !(dataItem = dataItemIt->second.lock())) { auto dataItemKey = splitKey(key); - string device { dataItemKey.second.value_or(m_defaultDevice.value_or("")) }; + string device {dataItemKey.second.value_or(m_defaultDevice.value_or(""))}; dataItem = m_contract->findDataItem(device, dataItemKey.first); if (dataItem == nullptr) @@ -290,7 +290,8 @@ namespace mtconnect { if (reqs != nullptr) { - auto obs = zipProperties(dataItem, timestamp, *reqs, token, end, errors, m_contract->getSchemaVersion()); + auto obs = zipProperties(dataItem, timestamp, *reqs, token, end, errors, + m_contract->getSchemaVersion()); if (dataItem->getConstantValue()) return nullptr; if (obs && source) diff --git a/test_package/change_observer_test.cpp b/test_package/change_observer_test.cpp index 88d48ead..c615aba6 100644 --- a/test_package/change_observer_test.cpp +++ b/test_package/change_observer_test.cpp @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) namespace mtconnect { using namespace observation; - + class ChangeObserverTest : public testing::Test { public: @@ -54,7 +54,7 @@ namespace mtconnect { void SetUp() override { m_context = make_unique(); - m_strand = make_unique < boost::asio::io_context::strand>(*m_context); + m_strand = make_unique(*m_context); m_signaler = std::make_unique(); m_guard.emplace(m_context->get_executor()); } @@ -94,7 +94,7 @@ namespace mtconnect { auto startTime = std::chrono::system_clock::now(); std::chrono::milliseconds duration; - + changeObserver.m_handler = [&](boost::system::error_code ec) { EXPECT_EQ(boost::asio::error::operation_aborted, ec); duration = std::chrono::duration_cast( @@ -126,7 +126,8 @@ namespace mtconnect { std::chrono::system_clock::now() - startTime); ASSERT_FALSE(changeObserver.wasSignaled()); }; - auto waitResult = changeObserver.waitForSignal((expectedExeTime / 2)); // Wait to be signalled within twice expected time + auto waitResult = changeObserver.waitForSignal( + (expectedExeTime / 2)); // Wait to be signalled within twice expected time // Only wait a maximum of 1 / 2 the expected time m_context->run_until(startTime + expectedExeTime); @@ -168,7 +169,8 @@ namespace mtconnect { ASSERT_TRUE(changeObserver.wasSignaled()); called = true; }; - ASSERT_TRUE(changeObserver.waitForSignal(waitTime)); // Wait to be signalled within twice expected time + ASSERT_TRUE( + changeObserver.waitForSignal(waitTime)); // Wait to be signalled within twice expected time m_context->run_for(50ms); m_signaler->signalObservers(uint64_t {100}); @@ -192,11 +194,12 @@ namespace mtconnect { auto const waitTime = 2000ms; bool called {false}; changeObserver.m_handler = [&](boost::system::error_code ec) { - EXPECT_EQ(boost::asio::error::operation_aborted, ec); - ASSERT_TRUE(changeObserver.wasSignaled()); - called = true; + EXPECT_EQ(boost::asio::error::operation_aborted, ec); + ASSERT_TRUE(changeObserver.wasSignaled()); + called = true; }; - ASSERT_TRUE(changeObserver.waitForSignal(waitTime)); // Wait to be signalled within twice expected time + ASSERT_TRUE( + changeObserver.waitForSignal(waitTime)); // Wait to be signalled within twice expected time m_context->run_for(50ms); m_signaler->signalObservers(uint64_t {100}); @@ -313,7 +316,7 @@ namespace mtconnect { ASSERT_FALSE(called); expected = addObservations(1); - ASSERT_EQ(4ull, expected); + ASSERT_EQ(4ull, expected); m_context->run_for(200ms); ASSERT_FALSE(called); @@ -359,7 +362,7 @@ namespace mtconnect { m_context->run_for(100ms); ASSERT_FALSE(called); - + waitFor([&called]() { return called; }); ASSERT_TRUE(called); } @@ -368,13 +371,13 @@ namespace mtconnect { { FilterSet filter {"a", "b"}; shared_ptr observer { - make_shared(*m_strand, m_buffer, std::move(filter), 200ms, 500ms)}; - + make_shared(*m_strand, m_buffer, std::move(filter), 200ms, 500ms)}; + addObservations(3); observer->observe(1, [this](const string &id) { return m_signalers[id].get(); }); - + ASSERT_FALSE(observer->isEndOfBuffer()); - + bool called {false}; SequenceNumber_t expected = 1; bool end = false; @@ -386,26 +389,26 @@ namespace mtconnect { asio::post(*m_strand, boost::bind(&AsyncObserver::handlerCompleted, observer)); return expected + 1; }; - + observer->handlerCompleted(); ASSERT_TRUE(called); ASSERT_FALSE(observer->isEndOfBuffer()); waitFor([&] { return called; }); - + called = false; expected = 2; waitFor([&] { return called; }); ASSERT_TRUE(called); ASSERT_EQ(3, observer->getSequence()); ASSERT_FALSE(observer->isEndOfBuffer()); - + called = false; expected = 3; waitFor([&] { return called; }); ASSERT_TRUE(called); ASSERT_EQ(4, observer->getSequence()); ASSERT_TRUE(observer->isEndOfBuffer()); - + end = true; called = false; expected = 4; @@ -413,10 +416,10 @@ namespace mtconnect { ASSERT_FALSE(called); ASSERT_EQ(4, observer->getSequence()); ASSERT_TRUE(observer->isEndOfBuffer()); - + auto s = addObservations(3); ASSERT_EQ(6ull, s); - + called = false; expected = 4; waitFor([&] { return called; }); diff --git a/test_package/checkpoint_test.cpp b/test_package/checkpoint_test.cpp index 5af6a77b..e810b8d3 100644 --- a/test_package/checkpoint_test.cpp +++ b/test_package/checkpoint_test.cpp @@ -580,3 +580,126 @@ TEST_F(CheckpointTest, orphaned_observations_should_be_skipped) ASSERT_EQ(0, list2.size()); } + +TEST_F(CheckpointTest, condition_chaining_with_condition_id_for_23) +{ + entity::ErrorList errors; + Timestamp time = Timestamp(date::sys_days(2021_y / jan / 19_d)) + 10h + 1min; + auto warning1 = entity::Properties { + {"level", "WARNING"s}, + {"conditionId", "CODE1"s}, + {"qualifier", "HIGH"s}, + {"VALUE", "Over..."s}, + }; + auto warning2 = entity::Properties { + {"level", "WARNING"s}, + {"conditionId", "CODE2"s}, + {"qualifier", "HIGH"s}, + {"VALUE", "Over..."s}, + }; + auto warning3 = entity::Properties { + {"level", "WARNING"s}, + {"conditionId", "CODE3"s}, + {"qualifier", "HIGH"s}, + {"VALUE", "Over..."s}, + }; + auto fault2 = entity::Properties { + {"level", "FAULT"s}, + {"conditionId", "CODE2"s}, + {"qualifier", "HIGH"s}, + {"VALUE", "Over..."s}, + }; + auto normal = entity::Properties {{"level", "NORMAL"s}}; + auto normal1 = entity::Properties {{"conditionId", "CODE1"s}, {"level", "NORMAL"s}}; + auto normal2 = entity::Properties {{"conditionId", "CODE2"s}, {"level", "NORMAL"s}}; + auto unavailable = entity::Properties {{"level", "UNAVAILABLE"s}}; + auto value = entity::Properties {{"VALUE", "123"s}}; + + FilterSet filter; + filter.insert(m_dataItem1->getId()); + ObservationList list; + + auto p1 = observation::Observation::make(m_dataItem1, warning1, time, errors); + m_checkpoint->addObservation(p1); + ASSERT_EQ(2, p1.use_count()); + + m_checkpoint->getObservations(list); + ASSERT_EQ(1, list.size()); + list.clear(); + + auto p2 = observation::Observation::make(m_dataItem1, warning2, time, errors); + m_checkpoint->addObservation(p2); + ASSERT_EQ(2, p2.use_count()); + ASSERT_EQ(2, p1.use_count()); + + list.clear(); + m_checkpoint->getObservations(list); + ASSERT_EQ(2, list.size()); + ASSERT_EQ(p1, dynamic_pointer_cast(p2)->getPrev()); + list.clear(); + + auto p3 = observation::Observation::make(m_dataItem1, warning3, time, errors); + m_checkpoint->addObservation(p3); + ASSERT_EQ(2, p3.use_count()); + ASSERT_EQ(2, p2.use_count()); + ASSERT_EQ(2, p1.use_count()); + + ASSERT_EQ(p2, dynamic_pointer_cast(p3)->getPrev()); + ASSERT_EQ(p1, dynamic_pointer_cast(p2)->getPrev()); + ASSERT_FALSE(dynamic_pointer_cast(p1)->getPrev()); + + list.clear(); + m_checkpoint->getObservations(list); + ASSERT_EQ(3, list.size()); + list.clear(); + + // Replace Warning on CODE 2 with a fault + auto p4 = observation::Observation::make(m_dataItem1, fault2, time, errors); + m_checkpoint->addObservation(p4); + ASSERT_EQ(2, p4.use_count()); + ASSERT_EQ(1, p3.use_count()); + ASSERT_EQ(2, p2.use_count()); + ASSERT_EQ(2, p1.use_count()); + + // Should have been deep copyied + ASSERT_NE(p3, Cond(p4)->getPrev()); + + // Codes should still match + ASSERT_EQ(Cond(p3)->getCode(), Cond(p4)->getPrev()->getCode()); + ASSERT_EQ(2, Cond(p4)->getPrev().use_count()); + ASSERT_EQ(Cond(p1)->getCode(), Cond(p4)->getPrev()->getPrev()->getCode()); + ASSERT_EQ(2, Cond(p4)->getPrev()->getPrev().use_count()); + ASSERT_FALSE(Cond(p4)->getPrev()->getPrev()->getPrev()); + + list.clear(); + m_checkpoint->getObservations(list); + ASSERT_EQ(3, list.size()); + list.clear(); + + auto p5 = observation::Observation::make(m_dataItem1, normal2, time, errors); + m_checkpoint->addObservation(p5); + ASSERT_FALSE(Cond(p5)->getPrev()); + + // Check cleanup + ObservationPtr p7 = m_checkpoint->getObservations().at(std::string("1")); + ASSERT_TRUE(p7); + ASSERT_EQ(2, p7.use_count()); + ASSERT_NE(p5, p7); + ASSERT_EQ(std::string("CODE3"), Cond(p7)->getCode()); + ASSERT_EQ(std::string("CODE1"), Cond(p7)->getPrev()->getCode()); + ASSERT_FALSE(Cond(p7)->getPrev()->getPrev()); + + list.clear(); + m_checkpoint->getObservations(list); + ASSERT_EQ(2, list.size()); + list.clear(); + + // Clear all + auto p6 = observation::Observation::make(m_dataItem1, normal, time, errors); + m_checkpoint->addObservation(p6); + ASSERT_FALSE(Cond(p6)->getPrev()); + + list.clear(); + m_checkpoint->getObservations(list); + ASSERT_EQ(1, (int)list.size()); +} diff --git a/test_package/config_test.cpp b/test_package/config_test.cpp index e1888686..f116cccc 100644 --- a/test_package/config_test.cpp +++ b/test_package/config_test.cpp @@ -2200,14 +2200,14 @@ ServiceName="some_prefix_${CONFIG_TEST}_suffix" ASSERT_TRUE(m_config->getAgent()); } - + TEST_F(ConfigTest, should_support_json_format) { using namespace std::chrono_literals; string str("{ \"Devices\": \"" TEST_RESOURCE_DIR "/samples/test_config.xml\"," -R"DOC( + R"DOC( "Adapters": { "LinuxCNC": { "Port": 23, diff --git a/test_package/data_item_mapping_test.cpp b/test_package/data_item_mapping_test.cpp index d030f882..4a6c983d 100644 --- a/test_package/data_item_mapping_test.cpp +++ b/test_package/data_item_mapping_test.cpp @@ -44,7 +44,9 @@ int main(int argc, char *argv[]) class MockPipelineContract : public PipelineContract { public: - MockPipelineContract(std::map &items, int32_t schemaVersion) : m_dataItems(items), m_schemaVersion(schemaVersion) {} + MockPipelineContract(std::map &items, int32_t schemaVersion) + : m_dataItems(items), m_schemaVersion(schemaVersion) + {} DevicePtr findDevice(const std::string &) override { return nullptr; } DataItemPtr findDataItem(const std::string &device, const std::string &name) override { @@ -71,8 +73,7 @@ class DataItemMappingTest : public testing::Test void SetUp() override { m_context = make_shared(); - m_context->m_contract = make_unique(m_dataItems, - SCHEMA_VERSION(2, 0)); + m_context->m_contract = make_unique(m_dataItems, SCHEMA_VERSION(2, 0)); m_mapper = make_shared(m_context, "", 2); m_mapper->bind(make_shared(TypeGuard(RUN))); } @@ -540,7 +541,7 @@ TEST_F(DataItemMappingTest, continue_after_conversion_error) TEST_F(DataItemMappingTest, version_23_condition_behavior_with_native_code) { auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); - auto *context = dynamic_cast(m_context->m_contract.get()); + auto *context = dynamic_cast(m_context->m_contract.get()); context->m_schemaVersion = SCHEMA_VERSION(2, 3); // ||||| @@ -559,13 +560,12 @@ TEST_F(DataItemMappingTest, version_23_condition_behavior_with_native_code) ASSERT_EQ("A123", cond->get("conditionId")); ASSERT_EQ("HIGH", cond->get("qualifier")); ASSERT_EQ("Fault", cond->getName()); - } TEST_F(DataItemMappingTest, version_23_condition_behavior_with_condition_id) { auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); - auto *context = dynamic_cast(m_context->m_contract.get()); + auto *context = dynamic_cast(m_context->m_contract.get()); context->m_schemaVersion = SCHEMA_VERSION(2, 3); // ||||| @@ -584,13 +584,12 @@ TEST_F(DataItemMappingTest, version_23_condition_behavior_with_condition_id) ASSERT_EQ("B456", cond->get("conditionId")); ASSERT_EQ("HIGH", cond->get("qualifier")); ASSERT_EQ("Fault", cond->getName()); - } TEST_F(DataItemMappingTest, version_23_condition_behavior_with_only_condition_id) { auto di = makeDataItem({{"id", "a"s}, {"type", "POSITION"s}, {"category", "CONDITION"s}}); - auto *context = dynamic_cast(m_context->m_contract.get()); + auto *context = dynamic_cast(m_context->m_contract.get()); context->m_schemaVersion = SCHEMA_VERSION(2, 3); // ||||| @@ -609,6 +608,4 @@ TEST_F(DataItemMappingTest, version_23_condition_behavior_with_only_condition_id ASSERT_EQ("B456", cond->get("conditionId")); ASSERT_EQ("HIGH", cond->get("qualifier")); ASSERT_EQ("Fault", cond->getName()); - } - From 1a651544f26fe34ef1c733a32ec69454d9b7b89f Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Thu, 5 Oct 2023 22:21:49 +0200 Subject: [PATCH 04/12] initial device type rest argument --- src/mtconnect/agent.cpp | 16 +++-- src/mtconnect/agent.hpp | 9 ++- src/mtconnect/sink/rest_sink/rest_service.cpp | 71 +++++++++++++------ src/mtconnect/sink/rest_sink/rest_service.hpp | 18 +++-- src/mtconnect/sink/sink.hpp | 3 +- 5 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index e4db010f..3c51c800 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -1294,17 +1294,20 @@ namespace mtconnect { // Validation methods // ----------------------------------------------- - string Agent::devicesAndPath(const std::optional &path, const DevicePtr device) const + string Agent::devicesAndPath(const std::optional &path, const DevicePtr device, const std::optional &deviceType) const { string dataPath; - if (device) + if (device || deviceType) { string prefix; - if (device->getName() == "Agent") + if ((device && device->getName() == "Agent") || + (deviceType && *deviceType == "Agent")) prefix = "//Devices/Agent"; - else + else if (device) prefix = "//Devices/Device[@uuid=\"" + *device->getUuid() + "\"]"; + else if (deviceType) + prefix = "//Devices/Device"; if (path) { @@ -1324,7 +1327,10 @@ namespace mtconnect { } else { - dataPath = path ? *path : "//Devices/Device|//Devices/Agent"; + if (path) + dataPath = *path; + else + dataPath = "//Devices/Device|//Devices/Agent"; } return dataPath; diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp index a7e42865..6f14a079 100644 --- a/src/mtconnect/agent.hpp +++ b/src/mtconnect/agent.hpp @@ -421,9 +421,11 @@ namespace mtconnect { /// /// @param[in] path Optional path to prefix /// @param[in] device Optional device if one device is specified + /// @param[in] deviceType optional Agent or Device selector /// @return The rewritten path properly prefixed std::string devicesAndPath(const std::optional &path, - const DevicePtr device) const; + const DevicePtr device, + const std::optional &deviceType = std::nullopt) const; /// @brief Creates unique ids for the device model and maps to the originals /// @@ -636,9 +638,10 @@ namespace mtconnect { const PrinterMap &getPrinters() const override { return m_agent->getPrinters(); } void getDataItemsForPath(const DevicePtr device, const std::optional &path, - FilterSet &filter) const override + FilterSet &filter, + const std::optional &deviceType) const override { - std::string dataPath = m_agent->devicesAndPath(path, device); + std::string dataPath = m_agent->devicesAndPath(path, device, deviceType); const auto &parser = m_agent->getXmlParser(); parser->getDataItems(filter, dataPath); } diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index d72b808f..2e3647c7 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -101,7 +101,8 @@ namespace mtconnect { {"removed", QUERY, "Boolean indicating if removed assets are included in results"}, {"type", QUERY, "Only include assets of type `type` in the results"}, {"count", QUERY, "Maximum number of entities to include in results"}, - {"assetId", QUERY, "An assetId to select"}, + {"assetId", QUERY, "An assetId to select"}, + {"deviceType", QUERY, "Values are 'Device' or 'Agent'. Selects only devices of that type."}, {"assetId", PATH, "An assetId to select"}, {"path", QUERY, "XPath to filter DataItems matched against the probe document"}, {"at", QUERY, "Sequence number at which the observation snapshot is taken"}, @@ -423,35 +424,41 @@ namespace mtconnect { auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { auto device = request->parameter("device"); auto pretty = *request->parameter("pretty"); + auto deviceType = request->parameter("deviceType"); auto printer = printerForAccepts(request->m_accepts); if (device && !ends_with(request->m_path, string("probe")) && m_sinkContract->findDeviceByUUIDorName(*device) == nullptr) return false; + + if (deviceType && *deviceType != "Device" && *deviceType != "Agent") + { + return false; + } - respond(session, probeRequest(printer, device, pretty)); + respond(session, probeRequest(printer, device, pretty, deviceType)); return true; }; - m_server->addRouting({boost::beast::http::verb::get, "/probe?pretty={bool:false}", handler}) + m_server->addRouting({boost::beast::http::verb::get, "/probe?pretty={bool:false}&deviceType={string}", handler}) .document("MTConnect probe request", "Provides metadata service for the MTConnect Devices information model for all " "devices."); m_server ->addRouting( - {boost::beast::http::verb::get, "/{device}/probe?pretty={bool:false}", handler}) + {boost::beast::http::verb::get, "/{device}/probe?pretty={bool:false}&deviceType={string}", handler}) .document("MTConnect probe request", "Provides metadata service for the MTConnect Devices information model for " "device identified by `device` matching `name` or `uuid`."); // Must be last - m_server->addRouting({boost::beast::http::verb::get, "/?pretty={bool:false}", handler}) + m_server->addRouting({boost::beast::http::verb::get, "/?pretty={bool:false}&deviceType={string}", handler}) .document("MTConnect probe request", "Provides metadata service for the MTConnect Devices information model for all " "devices."); m_server - ->addRouting({boost::beast::http::verb::get, "/{device}?pretty={bool:false}", handler}) + ->addRouting({boost::beast::http::verb::get, "/{device}?pretty={bool:false}&deviceType={string}", handler}) .document("MTConnect probe request", "Provides metadata service for the MTConnect Devices information model for " "device identified by `device` matching `name` or `uuid`."); @@ -598,7 +605,8 @@ namespace mtconnect { streamCurrentRequest(session, printerForAccepts(request->m_accepts), *interval, request->parameter("device"), request->parameter("path"), - *request->parameter("pretty")); + *request->parameter("pretty"), + request->parameter("deviceType")); } else { @@ -606,14 +614,16 @@ namespace mtconnect { request->parameter("device"), request->parameter("at"), request->parameter("path"), - *request->parameter("pretty"))); + *request->parameter("pretty"), + request->parameter("deviceType"))); } return true; }; string qp( "path={string}&at={unsigned_integer}&" - "interval={integer}&pretty={bool:false}"); + "interval={integer}&pretty={bool:false}&" + "deviceType={string}"); m_server->addRouting({boost::beast::http::verb::get, "/current?" + qp, handler}) .document("MTConnect current request", "Gets a stapshot of the state of all the observations for all devices " @@ -635,7 +645,8 @@ namespace mtconnect { session, printerForAccepts(request->m_accepts), *interval, *request->parameter("heartbeat"), *request->parameter("count"), request->parameter("device"), request->parameter("from"), - request->parameter("path"), *request->parameter("pretty")); + request->parameter("path"), *request->parameter("pretty"), + request->parameter("deviceType")); } else { @@ -644,7 +655,8 @@ namespace mtconnect { printerForAccepts(request->m_accepts), *request->parameter("count"), request->parameter("device"), request->parameter("from"), request->parameter("to"), request->parameter("path"), - *request->parameter("pretty"))); + *request->parameter("pretty"), + request->parameter("deviceType"))); } return true; }; @@ -653,7 +665,8 @@ namespace mtconnect { "path={string}&from={unsigned_integer}&" "interval={integer}&count={integer:100}&" "heartbeat={integer:10000}&to={unsigned_integer}&" - "pretty={bool:false}"); + "pretty={bool:false}&" + "deviceType={string}"); m_server->addRouting({boost::beast::http::verb::get, "/sample?" + qp, handler}) .document("MTConnect sample request", "Gets a time series of at maximum `count` observations for all devices " @@ -711,7 +724,8 @@ namespace mtconnect { // ------------------------------------------- ResponsePtr RestService::probeRequest(const Printer *printer, - const std::optional &device, bool pretty) + const std::optional &device, bool pretty, + const std::optional &deviceType) { NAMED_SCOPE("RestService::probeRequest"); @@ -725,6 +739,12 @@ namespace mtconnect { else { deviceList = m_sinkContract->getDevices(); + if (deviceType) + { + deviceList.remove_if([&deviceType](const DevicePtr &dev) { + return dev->getName() == *deviceType; + }); + } } auto counts = m_sinkContract->getAssetStorage()->getCountsByType(); @@ -742,7 +762,8 @@ namespace mtconnect { ResponsePtr RestService::currentRequest(const Printer *printer, const std::optional &device, const std::optional &at, - const std::optional &path, bool pretty) + const std::optional &path, bool pretty, + const std::optional &deviceType) { using namespace rest_sink; DevicePtr dev {nullptr}; @@ -754,7 +775,7 @@ namespace mtconnect { if (path || device) { filter = make_optional(); - checkPath(printer, path, dev, *filter); + checkPath(printer, path, dev, *filter, deviceType); } // Check if there is a frequency to stream data or not @@ -767,7 +788,8 @@ namespace mtconnect { const std::optional &device, const std::optional &from, const std::optional &to, - const std::optional &path, bool pretty) + const std::optional &path, bool pretty, + const std::optional &deviceType) { using namespace rest_sink; DevicePtr dev {nullptr}; @@ -779,7 +801,7 @@ namespace mtconnect { if (path || device) { filter = make_optional(); - checkPath(printer, path, dev, *filter); + checkPath(printer, path, dev, *filter, deviceType); } // Check if there is a frequency to stream data or not @@ -848,7 +870,8 @@ namespace mtconnect { const int interval, const int heartbeatIn, const int count, const std::optional &device, const std::optional &from, - const std::optional &path, bool pretty) + const std::optional &path, bool pretty, + const std::optional &deviceType) { NAMED_SCOPE("RestService::streamSampleRequest"); @@ -865,7 +888,7 @@ namespace mtconnect { } FilterSet filter; - checkPath(printer, path, dev, filter); + checkPath(printer, path, dev, filter, deviceType); auto asyncResponse = make_shared( m_strand, m_sinkContract->getCircularBuffer(), std::move(filter), @@ -939,7 +962,8 @@ namespace mtconnect { void RestService::streamCurrentRequest(SessionPtr session, const Printer *printer, const int interval, const std::optional &device, - const std::optional &path, bool pretty) + const std::optional &path, bool pretty, + const std::optional &deviceType) { checkRange(printer, interval, 0, numeric_limits().max(), "interval"); DevicePtr dev {nullptr}; @@ -952,7 +976,7 @@ namespace mtconnect { if (path || device) { asyncResponse->m_filter = make_optional(); - checkPath(printer, path, dev, *asyncResponse->m_filter); + checkPath(printer, path, dev, *asyncResponse->m_filter, deviceType); } asyncResponse->m_interval = chrono::milliseconds {interval}; asyncResponse->m_printer = printer; @@ -1280,11 +1304,12 @@ namespace mtconnect { } void RestService::checkPath(const Printer *printer, const std::optional &path, - const DevicePtr device, FilterSet &filter) const + const DevicePtr device, FilterSet &filter, + const std::optional &deviceType) const { try { - m_sinkContract->getDataItemsForPath(device, path, filter); + m_sinkContract->getDataItemsForPath(device, path, filter, deviceType); } catch (exception &e) { diff --git a/src/mtconnect/sink/rest_sink/rest_service.hpp b/src/mtconnect/sink/rest_sink/rest_service.hpp index 42e80f0f..3e8091cb 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.hpp +++ b/src/mtconnect/sink/rest_sink/rest_service.hpp @@ -100,7 +100,8 @@ namespace mtconnect { /// @return MTConnect Devices response ResponsePtr probeRequest(const printer::Printer *p, const std::optional &device = std::nullopt, - bool pretty = false); + bool pretty = false, + const std::optional &deviceType = std::nullopt); /// @brief Handler for a current request /// @param[in] p printer for doc generation @@ -113,7 +114,8 @@ namespace mtconnect { const std::optional &device = std::nullopt, const std::optional &at = std::nullopt, const std::optional &path = std::nullopt, - bool pretty = false); + bool pretty = false, + const std::optional &deviceType = std::nullopt); /// @brief Handler for a sample request /// @param[in] p printer for doc generation @@ -129,7 +131,8 @@ namespace mtconnect { const std::optional &from = std::nullopt, const std::optional &to = std::nullopt, const std::optional &path = std::nullopt, - bool pretty = false); + bool pretty = false, + const std::optional &deviceType = std::nullopt); /// @brief Handler for a streaming sample /// @param[in] session session to stream data to /// @param[in] p printer for doc generation @@ -145,7 +148,8 @@ namespace mtconnect { const std::optional &device = std::nullopt, const std::optional &from = std::nullopt, const std::optional &path = std::nullopt, - bool pretty = false); + bool pretty = false, + const std::optional &deviceType = std::nullopt); /// @brief Handler for a streaming current /// @param[in] session session to stream data to @@ -157,7 +161,8 @@ namespace mtconnect { void streamCurrentRequest(SessionPtr session, const printer::Printer *p, const int interval, const std::optional &device = std::nullopt, const std::optional &path = std::nullopt, - bool pretty = false); + bool pretty = false, + const std::optional &deviceType = std::nullopt); /// @brief Handler for put/post observation /// @param[in] p printer for response generation /// @param[in] device device @@ -308,7 +313,8 @@ namespace mtconnect { const std::string ¶m, bool notZero = false) const; void checkPath(const printer::Printer *printer, const std::optional &path, - const DevicePtr device, FilterSet &filter) const; + const DevicePtr device, FilterSet &filter, + const std::optional &deviceType = std::nullopt) const; DevicePtr checkDevice(const printer::Printer *printer, const std::string &uuid) const; diff --git a/src/mtconnect/sink/sink.hpp b/src/mtconnect/sink/sink.hpp index 7fd6b410..5eb44ed2 100644 --- a/src/mtconnect/sink/sink.hpp +++ b/src/mtconnect/sink/sink.hpp @@ -88,7 +88,8 @@ namespace mtconnect { /// @param[out] filter the set of all data items matching path to use for filtering virtual void getDataItemsForPath(const DevicePtr device, const std::optional &path, - FilterSet &filter) const = 0; + FilterSet &filter, + const std::optional &deviceType = std::nullopt) const = 0; /// @brief Add a source for this sink. /// /// This is used to create loopback sources for a sink From cac6065649a5e94f746a79be29efdb367dfa2449 Mon Sep 17 00:00:00 2001 From: dwickelhaus Date: Mon, 16 Oct 2023 14:38:50 -0400 Subject: [PATCH 05/12] Updated condition shdr format for the addition of condition_id to the native_code field --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ec819bc..2b720d3a 100755 --- a/README.md +++ b/README.md @@ -996,9 +996,11 @@ If the value itself contains a pipe character `|` the pipe must be escaped using 2009-06-15T00:00:00.000000|description|"Text with \| (pipe) character." -Conditions require seven (7) fields as follows: +Conditions require six (6) fields as follows: - ||||||| + |||||| + for adapters providing conditionIds - the conditionId is added to the native_code field with a ':' delimiter. + ||||||> For a complete description of these fields, see the standard. An example line will look like this: From bf04910a4b14ed4c1d708c012f841a90e6bf8436 Mon Sep 17 00:00:00 2001 From: dwickelhaus Date: Tue, 17 Oct 2023 11:25:42 -0400 Subject: [PATCH 06/12] Corrected SHDR pattern conditions and the condtion_id parameter Corrected path to conan profiles for the Linux and MAC builds, the Windows paths were correct. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b720d3a..c49ae2c7 100755 --- a/README.md +++ b/README.md @@ -999,8 +999,17 @@ If the value itself contains a pipe character `|` the pipe must be escaped using Conditions require six (6) fields as follows: |||||| - for adapters providing conditionIds - the conditionId is added to the native_code field with a ':' delimiter. - ||||||> + + * 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: From 9a8e22a92531ad1fdd086e766ace08c4203efa11 Mon Sep 17 00:00:00 2001 From: dwickelhaus Date: Tue, 17 Oct 2023 11:34:40 -0400 Subject: [PATCH 07/12] Removed * from the comments under the adapter condition shdr examples. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c49ae2c7..11a7838d 100755 --- a/README.md +++ b/README.md @@ -1000,15 +1000,15 @@ Conditions require six (6) fields as follows: |||||| - * Condition id and native code are set to the same value given as + 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 set to native_code |||||| - * Condition id is set to condition_id and native code is not set + 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: @@ -1482,7 +1482,7 @@ to instruct conan to not parallelize the builds. Some of the modules that includ ### Build the agent - conan create cppagent -pr cppagent/conan/profile/gcc --build=missing + conan create cppagent -pr cppagent/conan/profiles/gcc --build=missing ## Building on Mac OS @@ -1503,11 +1503,11 @@ Install brew and xcode command line tools ### Build the agent - conan create cppagent -pr cppagent/conan/profile/macos --build=missing + conan create cppagent -pr cppagent/conan/profiles/macos --build=missing ### Generate an xcode project for debugging - conan build . -pr conan/profile/xcode -s build_type=Debug --build=missing -o development=True + conan build . -pr conan/profiles/xcode -s build_type=Debug --build=missing -o development=True ## Building on Fedora Alpine @@ -1527,7 +1527,7 @@ Install brew and xcode command line tools ### Build the agent - conan create cppagent -pr cppagent/conan/profile/gcc --build=missing + conan create cppagent -pr cppagent/conan/profiles/gcc --build=missing ## For some examples, see the CI/CD workflows in `.github/workflows/build.yml` From eb15f9ff6469f182cea0880bf1997c5cf122a27e Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 21 Oct 2023 22:18:59 -0700 Subject: [PATCH 08/12] added deviceType and tests --- src/mtconnect/sink/rest_sink/rest_service.cpp | 8 +- test_package/agent_device_test.cpp | 97 +++++++++++++++++-- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index c367868f..ee722b78 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -742,7 +742,7 @@ namespace mtconnect { if (deviceType) { deviceList.remove_if([&deviceType](const DevicePtr &dev) { - return dev->getName() == *deviceType; + return dev->getName() != *deviceType; }); } } @@ -772,7 +772,7 @@ namespace mtconnect { dev = checkDevice(printer, *device); } FilterSetOpt filter; - if (path || device) + if (path || device || deviceType) { filter = make_optional(); checkPath(printer, path, dev, *filter, deviceType); @@ -798,7 +798,7 @@ namespace mtconnect { dev = checkDevice(printer, *device); } FilterSetOpt filter; - if (path || device) + if (path || device || deviceType) { filter = make_optional(); checkPath(printer, path, dev, *filter, deviceType); @@ -1004,7 +1004,7 @@ namespace mtconnect { } auto asyncResponse = make_shared(session, m_context); - if (path || device) + if (path || device || deviceType) { asyncResponse->m_filter = make_optional(); checkPath(printer, path, dev, *asyncResponse->m_filter, deviceType); diff --git a/test_package/agent_device_test.cpp b/test_package/agent_device_test.cpp index c2adc20b..d73f2a2b 100644 --- a/test_package/agent_device_test.cpp +++ b/test_package/agent_device_test.cpp @@ -15,6 +15,9 @@ // limitations under the License. // +/// @file +/// Tests related to agent device + // 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!) @@ -121,14 +124,16 @@ class AgentDeviceTest : public testing::Test string m_line; }; -TEST_F(AgentDeviceTest, AgentDeviceCreation) +/// @test check if the agent device was added to the agent +TEST_F(AgentDeviceTest, should_create_the_agent_device) { ASSERT_NE(nullptr, m_agentDevice); ASSERT_EQ(2, m_agentTestHelper->m_agent->getDevices().size()); ASSERT_EQ("Agent", m_agentDevice->getName().str()); } -TEST_F(AgentDeviceTest, VerifyRequiredDataItems) +/// @test check that the data items for agent and device added, removed, and changed were added +TEST_F(AgentDeviceTest, should_add_data_items_to_the_agent_devcie) { ASSERT_NE(nullptr, m_agentDevice); auto avail = m_agentDevice->getDeviceDataItem("agent_avail"); @@ -148,7 +153,8 @@ TEST_F(AgentDeviceTest, VerifyRequiredDataItems) ASSERT_EQ("DEVICE_CHANGED", changed->getType()); } -TEST_F(AgentDeviceTest, DeviceAddedItemsInBuffer) +/// @test verify device added was updated +TEST_F(AgentDeviceTest, should_have_device_added_in_buffer) { auto agent = m_agentTestHelper->getAgent(); auto device = agent->findDeviceByUUIDorName("000"); @@ -180,7 +186,8 @@ TEST_F(AgentDeviceTest, DeviceAddedItemsInBuffer) #define ID_PREFIX "_d0c33d4315" -TEST_F(AgentDeviceTest, AdapterAddedProbeTest) +/// @test verify adapter component is added +TEST_F(AgentDeviceTest, should_add_component_and_data_items_for_adapter) { m_port = 21788; addAdapter(); @@ -203,7 +210,8 @@ TEST_F(AgentDeviceTest, AdapterAddedProbeTest) } } -TEST_F(AgentDeviceTest, adapter_component_with_ip_address_suppressed) +/// @test check that the ip address was suppressed when requested +TEST_F(AgentDeviceTest, should_suppress_address_ip_address_when_configured) { m_port = 21788; addAdapter(true); @@ -222,7 +230,8 @@ TEST_F(AgentDeviceTest, adapter_component_with_ip_address_suppressed) #define AGENT_DEVICE_DEVICE_STREAM AGENT_DEVICE_STREAM "/m:ComponentStream[@component='Agent']" #define AGENT_DEVICE_ADAPTER_STREAM AGENT_DEVICE_STREAM "/m:ComponentStream[@component='Adapter']" -TEST_F(AgentDeviceTest, AdapterAddedCurrentTest) +/// @test verify the data items for the adapter were added and populated +TEST_F(AgentDeviceTest, should_observe_the_adapter_data_items) { { PARSE_XML_RESPONSE("/Agent/current"); @@ -245,7 +254,8 @@ TEST_F(AgentDeviceTest, AdapterAddedCurrentTest) } } -TEST_F(AgentDeviceTest, TestAdapterConnectionStatus) +/// @test checks the adapter connection status updates when the adapter connects and disconnects +TEST_F(AgentDeviceTest, should_track_adapter_connection_status) { srand(int32_t(chrono::system_clock::now().time_since_epoch().count())); m_port = rand() % 10000 + 5000; @@ -303,6 +313,7 @@ TEST_F(AgentDeviceTest, TestAdapterConnectionStatus) } } +/// @test verify the Agent Device uuid can be set in the configuration file TEST_F(AgentDeviceTest, verify_uuid_can_be_set_in_configuration) { m_agentTestHelper = make_unique(); @@ -314,3 +325,75 @@ TEST_F(AgentDeviceTest, verify_uuid_can_be_set_in_configuration) ASSERT_EQ("HELLO_KITTY", *m_agentDevice->getUuid()); } + +/// @test validate the use of deviceType rest parameter to select only the Agent or Devices for probe +TEST_F(AgentDeviceTest, should_only_return_only_devices_of_device_type_for_probe) +{ + using namespace mtconnect::sink::rest_sink; + + m_port = 21788; + addAdapter(); + { + QueryMap query {{"deviceType", "Agent"}}; + PARSE_XML_RESPONSE_QUERY("/probe", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:Device", 0); + ASSERT_XML_PATH_COUNT(doc, "//m:Agent", 1); + } + + { + QueryMap query {{"deviceType", "Device"}}; + PARSE_XML_RESPONSE_QUERY("/probe", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:Device", 1); + ASSERT_XML_PATH_COUNT(doc, "//m:Agent", 0); + } +} + +/// @test validate the use of deviceType rest parameter to select only the Agent or Devices for current +TEST_F(AgentDeviceTest, should_only_return_only_devices_of_device_type_for_current) +{ + using namespace mtconnect::sink::rest_sink; + + m_port = 21788; + addAdapter(); + { + QueryMap query {{"deviceType", "Agent"}}; + PARSE_XML_RESPONSE_QUERY("/current", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='Agent']", 1); + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='LinuxCNC']", 0); + } + + { + QueryMap query {{"deviceType", "Device"}}; + PARSE_XML_RESPONSE_QUERY("/current", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='Agent']", 0); + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='LinuxCNC']", 1); + } +} + +/// @test validate the use of deviceType rest parameter to select only the Agent or Devices for sample +TEST_F(AgentDeviceTest, should_only_return_only_devices_of_device_type_for_sample) +{ + using namespace mtconnect::sink::rest_sink; + + m_port = 21788; + addAdapter(); + { + QueryMap query {{"deviceType", "Agent"}}; + PARSE_XML_RESPONSE_QUERY("/sample", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='Agent']", 1); + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='LinuxCNC']", 0); + } + + { + QueryMap query {{"deviceType", "Device"}}; + PARSE_XML_RESPONSE_QUERY("/sample", query); + + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='Agent']", 0); + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream[@name='LinuxCNC']", 1); + } +} From 2742c25fe8f900a5d3a45bfae4e5e995078935d2 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sat, 21 Oct 2023 22:23:44 -0700 Subject: [PATCH 09/12] Version 2.3.0.0 RC 1 --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5212d8c3..7e9bf458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ # The version number. set(AGENT_VERSION_MAJOR 2) -set(AGENT_VERSION_MINOR 2) +set(AGENT_VERSION_MINOR 3) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 11) -set(AGENT_VERSION_RC "") +set(AGENT_VERSION_BUILD 0) +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) From 8bd58e4c76edf2a49db86dae05a718269b6a3a92 Mon Sep 17 00:00:00 2001 From: Dave Wickelhaus Date: Mon, 22 Jan 2024 21:42:38 -0500 Subject: [PATCH 10/12] On branch main-dev_readme_update Changes to be committed: modified: README.md Added from V2.2 README.md file * `AdapterIdentity` - Adapter Identity name used to prefix dataitems within the Agent device ids and names. #### MQTT Adapter/Source ### MQTT JSON Ingress Protocol Version 2.0 Corrected minor spelling errors. --- README.md | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/README.md b/README.md index be2f0939..c4ffa18c 100755 --- a/README.md +++ b/README.md @@ -948,6 +948,173 @@ Sinks { * `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` + > + + > **⚠️Note:** Currently, there is no JSON parser functionality. Agent is expecting a raw SHDR-formatted string + + Example mqtt adapter block: + ```json + mydevice { + Protocol = mqtt + MqttHost = localhost + MqttPort = 1883 + MqttClientId = myUniqueID + Topics = /ingest + } + ``` + +### MQTT JSON Ingress Protocol Version 2.0 + +In general the data format will be {"timestamp": "YYYY-MM-DDThh:mm:ssZ","dataItemId":"value", "dataItemId":{"key1":"value1", ..., "keyn":"valuen}} + +**NOTE**: See the standard for the complete description of the fields for the data item representations below. + +A simple set of events and samples will look something like this: + + ```json + { + "timestamp": "2023-11-06T12:12:44Z", //Time Stamp + "tempId": 22.6, //Temperature + "positionId": 1002.345, //X axis position + "executionId": "ACTIVE" //Execution state + } + ``` + +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" + } + } + ``` + +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 + } + } + ``` + + Example with the optional "resetTriggered" filed: + + ```json + { + "timestamp": "2023-11-09T11:20:00Z", + "cncregisterset1": { + "resetTriggered": "NEW", + "value": {"r1":"v1", "r2":"v2", "r3":"v3" } + } + } + ``` + +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 + } + } + } + ``` + + 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 + } + } + } + ``` + + ### Agent Adapter Configuration From 615bb8c0d498727219f7c56ccddcef1a9ca735f9 Mon Sep 17 00:00:00 2001 From: Dave Wickelhaus Date: Fri, 26 Jan 2024 12:09:46 -0500 Subject: [PATCH 11/12] On branch main-dev_readme_update Your branch is up to date with 'origin/main-dev_readme_update'. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes to be committed: modified: README.md * changed README.md version from 2.2 to 2.3 * Removed ⚠️Note:** Currently, there is no JSON parser functionality. Agent is expecting a raw SHDR-formatted string from MQTT Adapter/Source --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c4ffa18c..3c5457c0 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -MTConnect C++ Agent Version 2.2 +MTConnect C++ Agent Version 2.3 -------- [![Build MTConnect C++ Agent](https://github.com/mtconnect/cppagent/actions/workflows/build.yml/badge.svg)](https://github.com/mtconnect/cppagent/actions/workflows/build.yml) @@ -981,8 +981,6 @@ Sinks { > **⚠️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` > - > **⚠️Note:** Currently, there is no JSON parser functionality. Agent is expecting a raw SHDR-formatted string - Example mqtt adapter block: ```json mydevice { From 0bf7711fa49ad47e99cf296e96258a9429376839 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 16 Feb 2024 22:24:26 -0500 Subject: [PATCH 12/12] fixed merge of json mapper test --- test_package/json_mapping_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test_package/json_mapping_test.cpp b/test_package/json_mapping_test.cpp index e2dfffd8..6a33d2d0 100644 --- a/test_package/json_mapping_test.cpp +++ b/test_package/json_mapping_test.cpp @@ -65,6 +65,7 @@ class MockPipelineContract : public PipelineContract void deliverConnectStatus(entity::EntityPtr, const StringList &, bool) override {} void sourceFailed(const std::string &id) override {} const ObservationPtr checkDuplicate(const ObservationPtr &obs) const override { return obs; } + int32_t getSchemaVersion() const override { return SCHEMA_VERSION(2, 3); }; std::map &m_dataItems; std::map &m_devices;