Skip to content

525 data sets in configuration #529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set(AGENT_VERSION_MAJOR 2)
set(AGENT_VERSION_MINOR 5)
set(AGENT_VERSION_PATCH 0)
set(AGENT_VERSION_BUILD 1)
set(AGENT_VERSION_BUILD 2)
set(AGENT_VERSION_RC "")

# This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent
Expand Down
6 changes: 3 additions & 3 deletions docker/alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ 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'
ENV HOME='/root'
ENV CONAN_PROFILE="$HOME/agent/cppagent/conan/profiles/docker"

# update os and add dependencies
# note: Dockerfiles run as root by default, so don't need sudo
Expand Down Expand Up @@ -90,7 +90,7 @@ RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \
-o agent_prefix=mtc \
-o cpack=True \
-o "with_ruby=$WITH_RUBY" \
-o cpack_destination=/root/agent \
-o "cpack_destination=$HOME/agent" \
-o cpack_name=dist \
-o cpack_generator=TGZ \
-pr "$CONAN_PROFILE" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ namespace mtconnect {
{
auto transformation = make_shared<Factory>(
Requirements {Requirement("Translation", ValueType::VECTOR, 3, false),
Requirement("Rotation", ValueType::VECTOR, 3, false)});
Requirement("Rotation", ValueType::VECTOR, 3, false),
Requirement("TranslationDataSet", ValueType::DATA_SET, false),
Requirement("RotationDataSet", ValueType::DATA_SET, false)});

auto coordinateSystem = make_shared<Factory>(Requirements {
Requirement("id", true), Requirement("name", false), Requirement("nativeName", false),
Expand All @@ -40,8 +42,10 @@ namespace mtconnect {
ControlledVocab {"WORLD", "BASE", "OBJECT", "TASK", "MECHANICAL_INTERFACE",
"TOOL", "MOBILE_PLATFORM", "MACHINE", "CAMERA"},
true),
Requirement("Origin", ValueType::VECTOR, 3, false),
Requirement("Description", false), Requirement("Origin", ValueType::VECTOR, 3, false),
Requirement("OriginDataSet", ValueType::DATA_SET, false),
Requirement("Transformation", ValueType::ENTITY, transformation, false)});
coordinateSystem->setOrder({"Description", "Origin", "OriginDataSet", "Transformation"});

coordinateSystems = make_shared<Factory>(Requirements {Requirement(
"CoordinateSystem", ValueType::ENTITY, coordinateSystem, 1, Requirement::Infinite)});
Expand Down
10 changes: 8 additions & 2 deletions src/mtconnect/device_model/configuration/motion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@ namespace mtconnect {
{
static auto transformation = make_shared<Factory>(
Requirements {Requirement("Translation", ValueType::VECTOR, 3, false),
Requirement("Rotation", ValueType::VECTOR, 3, false)});
Requirement("Rotation", ValueType::VECTOR, 3, false),
Requirement("TranslationDataSet", ValueType::DATA_SET, false),
Requirement("RotationDataSet", ValueType::DATA_SET, false)});

static auto motion = make_shared<Factory>(Requirements {
Requirement("id", true), Requirement("parentIdRef", false),
Requirement("coordinateSystemIdRef", true),
Requirement("type", ControlledVocab {"REVOLUTE", "CONTINUOUS", "PRISMATIC", "FIXED"},
true),
Requirement("actuation", ControlledVocab {"DIRECT", "VIRTUAL", "NONE"}, true),
Requirement("Description", false), Requirement("Axis", ValueType::VECTOR, 3, true),
Requirement("Description", false), Requirement("Axis", ValueType::VECTOR, 3, false),
Requirement("AxisDataSet", ValueType::DATA_SET, false),
Requirement("Origin", ValueType::VECTOR, 3, false),
Requirement("OriginDataSet", ValueType::DATA_SET, false),
Requirement("Transformation", ValueType::ENTITY, transformation, false)});
motion->setOrder(
{"Description", "Axis", "AxisDataSet", "Origin", "OriginDataSet", "Transformation"});

return motion;
}
Expand Down
18 changes: 18 additions & 0 deletions src/mtconnect/entity/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ namespace mtconnect {
return m_simpleProperties.count(name) > 0;
}

/// @brief is this requirement resolved with a data set
/// @param name the name of the property
/// @return `true` if this is a data set
bool isDataSet(const std::string &name) const { return m_dataSets.count(name) > 0; }

/// @brief is this requirement resolved with a table
/// @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 get the requirement pointer for a key
/// @param name the property key
/// @return requirement pointer
Expand Down Expand Up @@ -378,6 +388,12 @@ namespace mtconnect {
{
m_hasRaw = true;
}
else if (BaseValueType(r.getType()) == ValueType::DATA_SET)
{
m_dataSets.insert(r.getName());
if (r.getType() == ValueType::TABLE)
m_tables.insert(r.getName());
}
else
{
m_simpleProperties.insert(r.getName());
Expand Down Expand Up @@ -429,6 +445,8 @@ namespace mtconnect {
bool m_any {false};

std::set<std::string> m_propertySets;
std::set<std::string> m_dataSets;
std::set<std::string> m_tables;
std::set<std::string> m_simpleProperties;
std::set<std::string> m_properties;
};
Expand Down
6 changes: 5 additions & 1 deletion src/mtconnect/entity/requirement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ namespace mtconnect::entity {
};

/// @brief Mask for value types
const int16_t VALUE_TYPE_BASE = 0x0F;
const std::uint16_t VALUE_TYPE_BASE = 0x0F;
constexpr const ValueType BaseValueType(const ValueType value)
{
return ValueType(std::uint16_t(value) & VALUE_TYPE_BASE);
}

class Factory;
using FactoryPtr = std::shared_ptr<Factory>;
Expand Down
85 changes: 84 additions & 1 deletion src/mtconnect/entity/xml_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

#include "mtconnect/entity/xml_parser.hpp"

#include <boost/spirit/include/qi_numeric.hpp>
#include <boost/spirit/include/qi_parse.hpp>

#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
Expand Down Expand Up @@ -88,6 +91,81 @@ namespace mtconnect::entity {
return content;
}

template <typename P, typename A>
static inline bool isType(const string &str, const P &parser, A &value)
{
std::string::const_iterator first(str.cbegin()), last(str.cend());
return boost::spirit::qi::parse(first, last, parser, value) && first == last;
}

static void parseDataSet(xmlNodePtr node, DataSet &dataSet, bool table, bool cell = false)
{
for (xmlNodePtr child = node->children; child; child = child->next)
{
if (child->type == XML_ELEMENT_NODE &&
((!cell && xmlStrcmp(child->name, BAD_CAST "Entry") == 0) ||
(cell && xmlStrcmp(child->name, BAD_CAST "Cell") == 0)))
{
// Get the key for the entry
string key;
for (xmlAttrPtr attr = child->properties; attr; attr = attr->next)
{
if (attr->type == XML_ATTRIBUTE_NODE)
{
string name((const char *)attr->name);
if (name != "key")
{
throw EntityError("parseDataSet: Expecting ksy for data set Entry: " +
string((const char *)node->name));
}
key = string((const char *)attr->children->content);
break;
}
}

/// TODO: Add support for tables
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The support of tables was added, wasn’t it? 🤔 If ao, either remove TODO: or the whole line.

DataSetValue value;
auto valueNode = child->children;
if (valueNode)
{
if (table && !cell)
{
DataSet &ds = value.emplace<DataSet>();
parseDataSet(child, ds, true, true);
}
else if (valueNode->type == XML_TEXT_NODE)
{
string text = ((const char *)valueNode->content);
trim(text);

if (int64_t v; isType(text, boost::spirit::long_long, v))
{
value = v;
}
else if (double v; isType(text, boost::spirit::double_, v))
{
value = v;
}
else
{
value = text;
}
}
else
{
LOG(warning) << "Invalid content for data set";
}
}
dataSet.emplace(key, value);
}
else
{
throw EntityError("parseDataSet: Expecting Entry for data set: " +
string((const char *)node->name));
}
}
}

EntityPtr XmlParser::parseXmlNode(FactoryPtr factory, xmlNodePtr node, ErrorList &errors,
bool parseNamespaces)
{
Expand Down Expand Up @@ -165,7 +243,12 @@ namespace mtconnect::entity {
}
}

if (simple)
if (ef->isDataSet(name))
{
auto ds = &properties[name].emplace<DataSet>();
parseDataSet(child, *ds, ef->isTable(name));
}
else if (simple)
{
if (child->children != nullptr && child->children->content != nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/mtconnect/sink/rest_sink/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ namespace mtconnect::sink::rest_sink {
obj.AddPairs("description", "OK");
{
AutoJsonObject<T> obj(writer, "content");
{
{
AutoJsonObject<T> obj(writer, "text/plain");
{
AutoJsonObject<T> obj(writer, "schema");
Expand Down
67 changes: 67 additions & 0 deletions test_package/entity_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,70 @@ TEST_F(EntityParserTest, check_proper_line_truncation)
ASSERT_EQ("Description", entity->getName());
ASSERT_EQ("And some text", entity->getValue<string>());
}

TEST_F(EntityParserTest, should_parse_data_sets)
{
auto ds = make_shared<Factory>(Requirements {Requirement("DataSet", ValueType::DATA_SET, true)});
auto root = make_shared<Factory>(Requirements {{{"Root", ValueType::ENTITY, ds, true}}});

ErrorList errors;
entity::XmlParser parser;

auto doc = R"DOC(
<Root>
<DataSet>
<Entry key="text">abc</Entry>
<Entry key="int">101</Entry>
<Entry key="double">50.5</Entry>
</DataSet>
</Root>
)DOC";

auto entity = parser.parse(root, doc, errors);
ASSERT_EQ("Root", entity->getName());
auto set = entity->get<DataSet>("DataSet");
ASSERT_EQ("abc", set.get<string>("text"));
ASSERT_EQ(101, set.get<int64_t>("int"));
ASSERT_EQ(50.5, set.get<double>("double"));
}

TEST_F(EntityParserTest, should_parse_tables)
{
auto table = make_shared<Factory>(Requirements {Requirement("Table", ValueType::TABLE, true)});
auto root = make_shared<Factory>(Requirements {{{"Root", ValueType::ENTITY, table, true}}});

ErrorList errors;
entity::XmlParser parser;

auto doc = R"DOC(
<Root>
<Table>
<Entry key="A">
<Cell key="text">abc</Cell>
<Cell key="int">101</Cell>
<Cell key="double">50.5</Cell>
</Entry>
<Entry key="B">
<Cell key="text2">def</Cell>
<Cell key="int2">102</Cell>
<Cell key="double2">100.5</Cell>
</Entry>
</Table>
</Root>
)DOC";

auto entity = parser.parse(root, doc, errors);
ASSERT_EQ("Root", entity->getName());
auto set = entity->get<DataSet>("Table");
auto e1 = set.get<DataSet>("A");

ASSERT_EQ("abc", e1.get<string>("text"));
ASSERT_EQ(101, e1.get<int64_t>("int"));
ASSERT_EQ(50.5, e1.get<double>("double"));

auto e2 = set.get<DataSet>("B");

ASSERT_EQ("def", e2.get<string>("text2"));
ASSERT_EQ(102, e2.get<int64_t>("int2"));
ASSERT_EQ(100.5, e2.get<double>("double2"));
}
Loading