Skip to content

Commit 70715a5

Browse files
authored
Merge pull request #529 from mtconnect/525_data_sets_in_configuration
525 data sets in configuration
2 parents 80b803c + b669340 commit 70715a5

File tree

11 files changed

+399
-11
lines changed

11 files changed

+399
-11
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
set(AGENT_VERSION_MAJOR 2)
33
set(AGENT_VERSION_MINOR 5)
44
set(AGENT_VERSION_PATCH 0)
5-
set(AGENT_VERSION_BUILD 1)
5+
set(AGENT_VERSION_BUILD 2)
66
set(AGENT_VERSION_RC "")
77

88
# This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent

docker/alpine/Dockerfile

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ ARG CONAN_CPU_COUNT=2
4444
ARG WITH_RUBY='True'
4545

4646
# set some variables
47-
ENV PATH="$HOME/venv3.9/bin:$PATH"
48-
ENV CONAN_PROFILE='/root/agent/cppagent/conan/profiles/docker'
47+
ENV HOME='/root'
48+
ENV CONAN_PROFILE="$HOME/agent/cppagent/conan/profiles/docker"
4949

5050
# update os and add dependencies
5151
# note: Dockerfiles run as root by default, so don't need sudo
@@ -90,7 +90,7 @@ RUN if [ -z "$WITH_TESTS" ] || [ "$WITH_TESTS" = "false" ]; then \
9090
-o agent_prefix=mtc \
9191
-o cpack=True \
9292
-o "with_ruby=$WITH_RUBY" \
93-
-o cpack_destination=/root/agent \
93+
-o "cpack_destination=$HOME/agent" \
9494
-o cpack_name=dist \
9595
-o cpack_generator=TGZ \
9696
-pr "$CONAN_PROFILE" \

src/mtconnect/device_model/configuration/coordinate_systems.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ namespace mtconnect {
3030
{
3131
auto transformation = make_shared<Factory>(
3232
Requirements {Requirement("Translation", ValueType::VECTOR, 3, false),
33-
Requirement("Rotation", ValueType::VECTOR, 3, false)});
33+
Requirement("Rotation", ValueType::VECTOR, 3, false),
34+
Requirement("TranslationDataSet", ValueType::DATA_SET, false),
35+
Requirement("RotationDataSet", ValueType::DATA_SET, false)});
3436

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

4650
coordinateSystems = make_shared<Factory>(Requirements {Requirement(
4751
"CoordinateSystem", ValueType::ENTITY, coordinateSystem, 1, Requirement::Infinite)});

src/mtconnect/device_model/configuration/motion.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,23 @@ namespace mtconnect {
2727
{
2828
static auto transformation = make_shared<Factory>(
2929
Requirements {Requirement("Translation", ValueType::VECTOR, 3, false),
30-
Requirement("Rotation", ValueType::VECTOR, 3, false)});
30+
Requirement("Rotation", ValueType::VECTOR, 3, false),
31+
Requirement("TranslationDataSet", ValueType::DATA_SET, false),
32+
Requirement("RotationDataSet", ValueType::DATA_SET, false)});
3133

3234
static auto motion = make_shared<Factory>(Requirements {
3335
Requirement("id", true), Requirement("parentIdRef", false),
3436
Requirement("coordinateSystemIdRef", true),
3537
Requirement("type", ControlledVocab {"REVOLUTE", "CONTINUOUS", "PRISMATIC", "FIXED"},
3638
true),
3739
Requirement("actuation", ControlledVocab {"DIRECT", "VIRTUAL", "NONE"}, true),
38-
Requirement("Description", false), Requirement("Axis", ValueType::VECTOR, 3, true),
40+
Requirement("Description", false), Requirement("Axis", ValueType::VECTOR, 3, false),
41+
Requirement("AxisDataSet", ValueType::DATA_SET, false),
3942
Requirement("Origin", ValueType::VECTOR, 3, false),
43+
Requirement("OriginDataSet", ValueType::DATA_SET, false),
4044
Requirement("Transformation", ValueType::ENTITY, transformation, false)});
45+
motion->setOrder(
46+
{"Description", "Axis", "AxisDataSet", "Origin", "OriginDataSet", "Transformation"});
4147

4248
return motion;
4349
}

src/mtconnect/entity/factory.hpp

+18
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ namespace mtconnect {
138138
return m_simpleProperties.count(name) > 0;
139139
}
140140

141+
/// @brief is this requirement resolved with a data set
142+
/// @param name the name of the property
143+
/// @return `true` if this is a data set
144+
bool isDataSet(const std::string &name) const { return m_dataSets.count(name) > 0; }
145+
146+
/// @brief is this requirement resolved with a table
147+
/// @param name the name of the property
148+
/// @return `true` if this is a table
149+
bool isTable(const std::string &name) const { return m_tables.count(name) > 0; }
150+
141151
/// @brief get the requirement pointer for a key
142152
/// @param name the property key
143153
/// @return requirement pointer
@@ -378,6 +388,12 @@ namespace mtconnect {
378388
{
379389
m_hasRaw = true;
380390
}
391+
else if (BaseValueType(r.getType()) == ValueType::DATA_SET)
392+
{
393+
m_dataSets.insert(r.getName());
394+
if (r.getType() == ValueType::TABLE)
395+
m_tables.insert(r.getName());
396+
}
381397
else
382398
{
383399
m_simpleProperties.insert(r.getName());
@@ -429,6 +445,8 @@ namespace mtconnect {
429445
bool m_any {false};
430446

431447
std::set<std::string> m_propertySets;
448+
std::set<std::string> m_dataSets;
449+
std::set<std::string> m_tables;
432450
std::set<std::string> m_simpleProperties;
433451
std::set<std::string> m_properties;
434452
};

src/mtconnect/entity/requirement.hpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ namespace mtconnect::entity {
7676
};
7777

7878
/// @brief Mask for value types
79-
const int16_t VALUE_TYPE_BASE = 0x0F;
79+
const std::uint16_t VALUE_TYPE_BASE = 0x0F;
80+
constexpr const ValueType BaseValueType(const ValueType value)
81+
{
82+
return ValueType(std::uint16_t(value) & VALUE_TYPE_BASE);
83+
}
8084

8185
class Factory;
8286
using FactoryPtr = std::shared_ptr<Factory>;

src/mtconnect/entity/xml_parser.cpp

+84-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#include "mtconnect/entity/xml_parser.hpp"
1919

20+
#include <boost/spirit/include/qi_numeric.hpp>
21+
#include <boost/spirit/include/qi_parse.hpp>
22+
2023
#include <libxml/parser.h>
2124
#include <libxml/xpath.h>
2225
#include <libxml/xpathInternals.h>
@@ -88,6 +91,81 @@ namespace mtconnect::entity {
8891
return content;
8992
}
9093

94+
template <typename P, typename A>
95+
static inline bool isType(const string &str, const P &parser, A &value)
96+
{
97+
std::string::const_iterator first(str.cbegin()), last(str.cend());
98+
return boost::spirit::qi::parse(first, last, parser, value) && first == last;
99+
}
100+
101+
static void parseDataSet(xmlNodePtr node, DataSet &dataSet, bool table, bool cell = false)
102+
{
103+
for (xmlNodePtr child = node->children; child; child = child->next)
104+
{
105+
if (child->type == XML_ELEMENT_NODE &&
106+
((!cell && xmlStrcmp(child->name, BAD_CAST "Entry") == 0) ||
107+
(cell && xmlStrcmp(child->name, BAD_CAST "Cell") == 0)))
108+
{
109+
// Get the key for the entry
110+
string key;
111+
for (xmlAttrPtr attr = child->properties; attr; attr = attr->next)
112+
{
113+
if (attr->type == XML_ATTRIBUTE_NODE)
114+
{
115+
string name((const char *)attr->name);
116+
if (name != "key")
117+
{
118+
throw EntityError("parseDataSet: Expecting ksy for data set Entry: " +
119+
string((const char *)node->name));
120+
}
121+
key = string((const char *)attr->children->content);
122+
break;
123+
}
124+
}
125+
126+
/// TODO: Add support for tables
127+
DataSetValue value;
128+
auto valueNode = child->children;
129+
if (valueNode)
130+
{
131+
if (table && !cell)
132+
{
133+
DataSet &ds = value.emplace<DataSet>();
134+
parseDataSet(child, ds, true, true);
135+
}
136+
else if (valueNode->type == XML_TEXT_NODE)
137+
{
138+
string text = ((const char *)valueNode->content);
139+
trim(text);
140+
141+
if (int64_t v; isType(text, boost::spirit::long_long, v))
142+
{
143+
value = v;
144+
}
145+
else if (double v; isType(text, boost::spirit::double_, v))
146+
{
147+
value = v;
148+
}
149+
else
150+
{
151+
value = text;
152+
}
153+
}
154+
else
155+
{
156+
LOG(warning) << "Invalid content for data set";
157+
}
158+
}
159+
dataSet.emplace(key, value);
160+
}
161+
else
162+
{
163+
throw EntityError("parseDataSet: Expecting Entry for data set: " +
164+
string((const char *)node->name));
165+
}
166+
}
167+
}
168+
91169
EntityPtr XmlParser::parseXmlNode(FactoryPtr factory, xmlNodePtr node, ErrorList &errors,
92170
bool parseNamespaces)
93171
{
@@ -165,7 +243,12 @@ namespace mtconnect::entity {
165243
}
166244
}
167245

168-
if (simple)
246+
if (ef->isDataSet(name))
247+
{
248+
auto ds = &properties[name].emplace<DataSet>();
249+
parseDataSet(child, *ds, ef->isTable(name));
250+
}
251+
else if (simple)
169252
{
170253
if (child->children != nullptr && child->children->content != nullptr)
171254
{

src/mtconnect/sink/rest_sink/server.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ namespace mtconnect::sink::rest_sink {
314314
obj.AddPairs("description", "OK");
315315
{
316316
AutoJsonObject<T> obj(writer, "content");
317-
{
317+
{
318318
AutoJsonObject<T> obj(writer, "text/plain");
319319
{
320320
AutoJsonObject<T> obj(writer, "schema");

test_package/entity_parser_test.cpp

+67
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,70 @@ TEST_F(EntityParserTest, check_proper_line_truncation)
317317
ASSERT_EQ("Description", entity->getName());
318318
ASSERT_EQ("And some text", entity->getValue<string>());
319319
}
320+
321+
TEST_F(EntityParserTest, should_parse_data_sets)
322+
{
323+
auto ds = make_shared<Factory>(Requirements {Requirement("DataSet", ValueType::DATA_SET, true)});
324+
auto root = make_shared<Factory>(Requirements {{{"Root", ValueType::ENTITY, ds, true}}});
325+
326+
ErrorList errors;
327+
entity::XmlParser parser;
328+
329+
auto doc = R"DOC(
330+
<Root>
331+
<DataSet>
332+
<Entry key="text">abc</Entry>
333+
<Entry key="int">101</Entry>
334+
<Entry key="double">50.5</Entry>
335+
</DataSet>
336+
</Root>
337+
)DOC";
338+
339+
auto entity = parser.parse(root, doc, errors);
340+
ASSERT_EQ("Root", entity->getName());
341+
auto set = entity->get<DataSet>("DataSet");
342+
ASSERT_EQ("abc", set.get<string>("text"));
343+
ASSERT_EQ(101, set.get<int64_t>("int"));
344+
ASSERT_EQ(50.5, set.get<double>("double"));
345+
}
346+
347+
TEST_F(EntityParserTest, should_parse_tables)
348+
{
349+
auto table = make_shared<Factory>(Requirements {Requirement("Table", ValueType::TABLE, true)});
350+
auto root = make_shared<Factory>(Requirements {{{"Root", ValueType::ENTITY, table, true}}});
351+
352+
ErrorList errors;
353+
entity::XmlParser parser;
354+
355+
auto doc = R"DOC(
356+
<Root>
357+
<Table>
358+
<Entry key="A">
359+
<Cell key="text">abc</Cell>
360+
<Cell key="int">101</Cell>
361+
<Cell key="double">50.5</Cell>
362+
</Entry>
363+
<Entry key="B">
364+
<Cell key="text2">def</Cell>
365+
<Cell key="int2">102</Cell>
366+
<Cell key="double2">100.5</Cell>
367+
</Entry>
368+
</Table>
369+
</Root>
370+
)DOC";
371+
372+
auto entity = parser.parse(root, doc, errors);
373+
ASSERT_EQ("Root", entity->getName());
374+
auto set = entity->get<DataSet>("Table");
375+
auto e1 = set.get<DataSet>("A");
376+
377+
ASSERT_EQ("abc", e1.get<string>("text"));
378+
ASSERT_EQ(101, e1.get<int64_t>("int"));
379+
ASSERT_EQ(50.5, e1.get<double>("double"));
380+
381+
auto e2 = set.get<DataSet>("B");
382+
383+
ASSERT_EQ("def", e2.get<string>("text2"));
384+
ASSERT_EQ(102, e2.get<int64_t>("int2"));
385+
ASSERT_EQ(100.5, e2.get<double>("double2"));
386+
}

0 commit comments

Comments
 (0)