Skip to content

Commit aab0080

Browse files
authored
Merge pull request #576 from mtconnect/575_fix_quality_for_samples
Fixed validation for samples
2 parents bb0aa35 + 44f8dcb commit aab0080

File tree

7 files changed

+193
-51
lines changed

7 files changed

+193
-51
lines changed

CMakeLists.txt

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

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

src/mtconnect/configuration/agent_config.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,8 @@ namespace mtconnect::configuration {
940940

941941
loadAdapters(config, options);
942942

943+
m_afterAgentHooks.exec(*this);
944+
943945
#ifdef WITH_PYTHON
944946
configurePython(config, options);
945947
#endif

src/mtconnect/configuration/agent_config.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ namespace mtconnect {
9393
/// @brief Get the callback manager after the agent is created
9494
/// @return the callback manager
9595
auto &afterAgentHooks() { return m_afterAgentHooks; }
96+
/// @brief Get the callback manager after the config has completed
97+
/// @return the callback manager
98+
auto &afterConfigHooks() { return m_afterConfigHooks; }
9699
/// @brief Get the callback manager after the agent is started
97100
/// @return the callback manager
98101
auto &beforeStartHooks() { return m_beforeStartHooks; }
@@ -402,6 +405,7 @@ namespace mtconnect {
402405
#endif
403406

404407
HookManager<AgentConfiguration> m_afterAgentHooks;
408+
HookManager<AgentConfiguration> m_afterConfigHooks;
405409
HookManager<AgentConfiguration> m_beforeStartHooks;
406410
HookManager<AgentConfiguration> m_beforeStopHooks;
407411
};

src/mtconnect/device_model/device.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ namespace mtconnect {
8484
{
8585
if (auto it = m_dataItems.get<ByName>().find(*name); it != m_dataItems.get<ByName>().end())
8686
{
87-
LOG(warning) << "Device " << getName() << ": Duplicate source '" << *name
87+
LOG(warning) << "Device " << getName() << ": Duplicate name '" << *name
8888
<< "' found in data item '" << di->getId() << "'. Previous data item: '"
8989
<< it->lock()->getId() << '\'';
9090
LOG(warning) << " Name '" << *name

src/mtconnect/entity/entity.hpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,5 +716,77 @@ namespace mtconnect {
716716

717717
return changed;
718718
}
719+
720+
/// @brief variant visitor to output Value to stream
721+
struct StreamOutputVisitor
722+
{
723+
/// @brief constructor
724+
/// @param os the output stream
725+
StreamOutputVisitor(std::ostream& os) : m_os(os) {}
726+
727+
void operator()(const std::monostate&) {
728+
m_os << "null";
729+
}
730+
731+
void operator()(const EntityPtr& entity) {
732+
const auto &id = entity->getIdentity();
733+
m_os << "Entity(" << entity->getName() << ":";
734+
StreamOutputVisitor visitor(m_os);
735+
std::visit(visitor, id);
736+
m_os << ")";
737+
}
738+
739+
void operator()(const EntityList& list) {
740+
m_os << "EntityList[";
741+
for (auto e : list) {
742+
StreamOutputVisitor visitor(m_os);
743+
visitor(e);
744+
m_os << " ";
745+
}
746+
m_os << "]";
747+
}
748+
749+
void operator()(const DataSet& dataSet) {
750+
m_os << "DataSet(" << dataSet.size() << " items)";
751+
}
752+
753+
void operator()(const QName& qname) {
754+
m_os << qname.str();
755+
}
756+
757+
void operator()(const Vector& vec) {
758+
m_os << "Vector[";
759+
for (const auto &v : vec) {
760+
m_os << v << " ";
761+
}
762+
m_os << "]";
763+
}
764+
765+
template <typename T>
766+
void operator()(const T& value) {
767+
m_os << value;
768+
}
769+
770+
std::ostream& m_os;
771+
};
772+
773+
/// @brief output operator for Value
774+
/// @param os the output stream
775+
/// @param v the Value to output
776+
inline std::ostream& operator<<(std::ostream& os, const Value &v) {
777+
StreamOutputVisitor visitor(os);
778+
std::visit(visitor, v);
779+
return os;
780+
}
781+
782+
/// @brief output operator for Value
783+
/// @param os the output stream
784+
/// @param v the Value to output
785+
inline std::ostream& operator<<(std::ostream& os, const EntityPtr &v) {
786+
StreamOutputVisitor visitor(os);
787+
visitor(v);
788+
return os;
789+
}
790+
719791
} // namespace entity
720792
} // namespace mtconnect

src/mtconnect/pipeline/validator.hpp

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ namespace mtconnect::pipeline {
3737
public:
3838
Validator(const Validator &) = default;
3939
Validator(PipelineContextPtr context)
40-
: Transform("Validator"), m_contract(context->m_contract.get())
40+
: Transform("Validator"), m_contract(context->m_contract.get())
4141
{
4242
m_guard = TypeGuard<observation::Observation>(RUN) || TypeGuard<entity::Entity>(SKIP);
4343
}
44-
44+
4545
/// @brief validate the Event
4646
/// @param entity The Event entity
4747
/// @returns modified entity with quality and deprecated properties
@@ -50,78 +50,78 @@ namespace mtconnect::pipeline {
5050
using namespace observation;
5151
using namespace mtconnect::validation::observations;
5252
auto obs = std::dynamic_pointer_cast<Observation>(entity);
53-
53+
auto &value = obs->getValue();
54+
55+
bool valid = true;
5456
auto di = obs->getDataItem();
55-
if (obs->isUnavailable() || di->isDataSet())
56-
{
57-
obs->setProperty("quality", std::string("VALID"));
58-
}
59-
else if (auto evt = std::dynamic_pointer_cast<observation::Event>(obs))
57+
if (!obs->isUnavailable() && !di->isDataSet())
6058
{
61-
auto &value = evt->getValue<std::string>();
62-
63-
// Optimize
64-
auto vocab = ControlledVocabularies.find(evt->getName());
65-
if (vocab != ControlledVocabularies.end())
59+
if (auto evt = std::dynamic_pointer_cast<observation::Event>(obs))
6660
{
67-
auto &lits = vocab->second;
68-
if (lits.size() != 0)
61+
auto vocab = ControlledVocabularies.find(evt->getName());
62+
if (vocab != ControlledVocabularies.end())
6963
{
70-
auto lit = lits.find(value);
71-
if (lit != lits.end())
64+
auto sv = std::get_if<std::string>(&value);
65+
auto &lits = vocab->second;
66+
if (lits.size() != 0 && sv != nullptr)
7267
{
73-
// Check if it has not been introduced yet
74-
if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first)
68+
auto lit = lits.find(*sv);
69+
if (lit != lits.end())
7570
{
76-
evt->setProperty("quality", std::string("INVALID"));
71+
// Check if it has not been introduced yet
72+
if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first)
73+
valid = false;
74+
75+
// Check if deprecated
76+
if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second)
77+
{
78+
evt->setProperty("deprecated", true);
79+
}
7780
}
7881
else
7982
{
80-
evt->setProperty("quality", std::string("VALID"));
81-
}
82-
83-
// Check if deprecated
84-
if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second)
85-
{
86-
evt->setProperty("deprecated", true);
83+
valid = false;
8784
}
8885
}
89-
else
86+
else if (lits.size() != 0)
9087
{
91-
evt->setProperty("quality", std::string("INVALID"));
92-
// Log once
93-
auto &id = di->getId();
94-
if (m_logOnce.count(id) < 1)
95-
{
96-
LOG(warning) << "DataItem '" << id << "': Invalid value for '" << evt->getName()
97-
<< "': '" << evt->getValue<std::string>() << '\'';
98-
m_logOnce.insert(id);
99-
}
100-
else
101-
{
102-
LOG(trace) << "DataItem '" << id << "': Invalid value for '" << evt->getName()
103-
<< "': '" << evt->getValue<std::string>() << '\'';
104-
}
88+
valid = false;
10589
}
10690
}
10791
else
10892
{
109-
evt->setProperty("quality", std::string("VALID"));
93+
evt->setProperty("quality", std::string("UNVERIFIABLE"));
11094
}
11195
}
112-
else
96+
else if (auto spl = std::dynamic_pointer_cast<observation::Sample>(obs))
11397
{
114-
evt->setProperty("quality", std::string("UNVERIFIABLE"));
98+
if (!(spl->hasProperty("quality") || std::holds_alternative<double>(value) ||
99+
std::holds_alternative<int64_t>(value)))
100+
valid = false;
115101
}
116102
}
117-
else if (auto spl = std::dynamic_pointer_cast<observation::Sample>(obs))
103+
104+
if (!valid)
118105
{
106+
obs->setProperty("quality", std::string("INVALID"));
107+
// Log once
108+
auto &id = di->getId();
109+
if (m_logOnce.count(id) < 1)
110+
{
111+
LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName()
112+
<< "': '" << value << '\'';
113+
m_logOnce.insert(id);
114+
}
115+
else
116+
{
117+
LOG(trace) << "DataItem '" << id << "': Invalid value for '" << obs->getName();
118+
}
119119
}
120-
else
120+
else if (!obs->hasProperty("quality"))
121121
{
122122
obs->setProperty("quality", std::string("VALID"));
123123
}
124-
124+
125125
return next(std::move(obs));
126126
}
127127

test_package/observation_validation_test.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ TEST_F(ObservationValidationTest, should_be_invalid_if_entry_has_not_been_introd
246246
ASSERT_FALSE(evt->hasProperty("deprecated"));
247247
}
248248

249-
TEST_F(ObservationValidationTest, should_validate_data_item_types)
249+
TEST_F(ObservationValidationTest, should_validate_invalid_sample_value)
250250
{
251251
auto contract = static_cast<MockPipelineContract *>(m_context->m_contract.get());
252252
contract->m_schemaVersion = SCHEMA_VERSION(2, 5);
@@ -284,6 +284,59 @@ TEST_F(ObservationValidationTest, should_validate_data_item_types)
284284
}
285285
}
286286

287+
TEST_F(ObservationValidationTest, should_validate_sample)
288+
{
289+
auto contract = static_cast<MockPipelineContract *>(m_context->m_contract.get());
290+
contract->m_schemaVersion = SCHEMA_VERSION(2, 5);
291+
292+
shared_ptr<ShdrTokenMapper> mapper;
293+
mapper = make_shared<ShdrTokenMapper>(m_context, "", 2);
294+
mapper->bind(m_validator);
295+
296+
ErrorList errors;
297+
m_dataItem = DataItem::make(
298+
{{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}},
299+
errors);
300+
301+
auto ts = make_shared<Timestamped>();
302+
ts->m_tokens = {{"pos"s, "1.234"s}};
303+
ts->m_timestamp = chrono::system_clock::now();
304+
ts->setProperty("timestamp", ts->m_timestamp);
305+
306+
auto observations = (*mapper)(ts);
307+
auto &r = *observations;
308+
ASSERT_EQ(typeid(Observations), typeid(r));
309+
310+
auto oblist = observations->getValue<EntityList>();
311+
ASSERT_EQ(1, oblist.size());
312+
313+
auto oi = oblist.begin();
314+
315+
{
316+
auto sample = dynamic_pointer_cast<Sample>(*oi++);
317+
ASSERT_TRUE(sample);
318+
ASSERT_EQ(m_dataItem, sample->getDataItem());
319+
ASSERT_FALSE(sample->isUnavailable());
320+
321+
ASSERT_EQ("VALID", sample->get<string>("quality"));
322+
}
323+
}
324+
325+
TEST_F(ObservationValidationTest, should_validate_sample_with_int64_value)
326+
{
327+
ErrorList errors;
328+
m_dataItem = DataItem::make(
329+
{{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}},
330+
errors);
331+
332+
auto obs = Observation::make(m_dataItem, {{"VALUE", int64_t(100)}}, m_time, errors);
333+
334+
auto evt = (*m_validator)(std::move(obs));
335+
auto quality = evt->get<string>("quality");
336+
ASSERT_EQ("VALID", quality);
337+
}
338+
339+
287340
TEST_F(ObservationValidationTest, should_not_validate_if_validation_is_off)
288341
{
289342
auto contract = static_cast<MockPipelineContract *>(m_context->m_contract.get());
@@ -369,3 +422,14 @@ TEST_F(ObservationValidationTest, should_validate_json_data_item_types)
369422
ASSERT_EQ("INVALID", sample->get<string>("quality"));
370423
}
371424
}
425+
426+
/// @test Validate a sample
427+
TEST_F(ObservationValidationTest, should_validate_sample_double_value)
428+
{
429+
ErrorList errors;
430+
auto event = Observation::make(m_dataItem, {{"VALUE", "READY"s}}, m_time, errors);
431+
432+
auto evt = (*m_validator)(std::move(event));
433+
auto quality = evt->get<string>("quality");
434+
ASSERT_EQ("VALID", quality);
435+
}

0 commit comments

Comments
 (0)