diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 6fca3349..94766475 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -32,6 +32,9 @@ on: jobs: # this workflow contains a single job called "build" build: + # Do not build if it is an RC build + if: ${{ ! contains(github.ref_name, "RC") }} + # the type of runner that the job will run on runs-on: ubuntu-latest strategy: diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index a2eaa939..b298501e 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -56,9 +56,8 @@ #include "mtconnect/sink/rest_sink/file_cache.hpp" #include "mtconnect/sink/rest_sink/session.hpp" #include "mtconnect/utilities.hpp" -#include "mtconnect/version.h" - #include "mtconnect/validation/observations.hpp" +#include "mtconnect/version.h" using namespace std; @@ -171,13 +170,14 @@ namespace mtconnect { if (!m_observationsInitialized) { - if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && IsOptionSet(m_options, mtconnect::configuration::Validation)) + if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && + IsOptionSet(m_options, mtconnect::configuration::Validation)) { m_validation = false; for (auto &printer : m_printers) printer.second->setValidation(false); } - + for (auto device : m_deviceIndex) initializeDataItems(device); @@ -201,7 +201,6 @@ namespace mtconnect { } m_observationsInitialized = true; - } } diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index 3524d03d..44c02bec 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -27,37 +27,41 @@ namespace mtconnect::pipeline { using namespace entity; - - /// @brief Map a token list to data items or asset types + + /// @brief Validate obsrvations based on Controlled Vocabularies + /// + /// - Does not validate data sets and tables + /// - Validates all events, not samples or conditions class AGENT_LIB_API Validator : public Transform { public: Validator(const Validator &) = default; Validator(PipelineContextPtr context) - : Transform("Validator"), - m_contract(context->m_contract.get()) + : Transform("Validator"), m_contract(context->m_contract.get()) { m_guard = TypeGuard(RUN) || TypeGuard(SKIP); } - + + /// @brief validate the Event + /// @param entity The Event entity + /// @returns modified entity with quality and deprecated properties EntityPtr operator()(entity::EntityPtr &&entity) override { using namespace observation; using namespace mtconnect::validation::observations; auto evt = std::dynamic_pointer_cast(entity); - + auto di = evt->getDataItem(); - auto &type = di->getType(); - auto &value = evt->getValue(); - - if (value == "UNAVAILABLE") + if (evt->isUnavailable() || di->isDataSet()) { evt->setProperty("quality", std::string("VALID")); } else { + auto &value = evt->getValue(); + // Optimize - auto vocab = ControlledVocabularies.find(type); + auto vocab = ControlledVocabularies.find(evt->getName()); if (vocab != ControlledVocabularies.end()) { auto &lits = vocab->second; @@ -67,7 +71,12 @@ namespace mtconnect::pipeline { if (lit != lits.end()) { evt->setProperty("quality", std::string("VALID")); - // Check for deprecated + + // Check if deprecated + if (lit->second > 0 && m_contract->getSchemaVersion() > lit->second) + { + evt->setProperty("deprecated", true); + } } else { @@ -76,13 +85,14 @@ namespace mtconnect::pipeline { auto &id = di->getId(); if (m_logOnce.count(id) < 1) { - LOG(warning) << id << ": Invalid value for '" << type << "' " << evt->getValue(); + LOG(warning) << "DataItem '" << id << "': Invalid value for '" << evt->getName() + << "': '" << evt->getValue() << '\''; m_logOnce.insert(id); } else { - LOG(trace) << id << ": Invalid value for '" << type << "' " << evt->getValue(); - + LOG(trace) << "DataItem '" << id << "': Invalid value for '" << evt->getName() + << "': '" << evt->getValue() << '\''; } } } @@ -96,7 +106,7 @@ namespace mtconnect::pipeline { evt->setProperty("quality", std::string("UNVERIFIABLE")); } } - + return next(std::move(evt)); } diff --git a/src/mtconnect/printer/json_printer.cpp b/src/mtconnect/printer/json_printer.cpp index 2720d5ea..28720379 100644 --- a/src/mtconnect/printer/json_printer.cpp +++ b/src/mtconnect/printer/json_printer.cpp @@ -60,8 +60,7 @@ namespace mtconnect::printer { template inline void header(AutoJsonObject &obj, const string &version, const string &hostname, const uint64_t instanceId, const unsigned int bufferSize, - const string &schemaVersion, const string modelChangeTime, - bool validation) + const string &schemaVersion, const string modelChangeTime, bool validation) { obj.AddPairs("version", version, "creationTime", getCurrentTime(GMT), "testIndicator", false, "instanceId", instanceId, "sender", hostname, "schemaVersion", schemaVersion); @@ -72,7 +71,6 @@ namespace mtconnect::printer { obj.AddPairs("bufferSize", bufferSize); if (validation) obj.AddPairs("validation", true); - } template @@ -80,10 +78,10 @@ namespace mtconnect::printer { const string &hostname, const uint64_t instanceId, const unsigned int bufferSize, const unsigned int assetBufferSize, const unsigned int assetCount, const string &schemaVersion, - const string modelChangeTime, - const bool validation) + const string modelChangeTime, const bool validation) { - header(obj, version, hostname, instanceId, bufferSize, schemaVersion, modelChangeTime, validation); + header(obj, version, hostname, instanceId, bufferSize, schemaVersion, modelChangeTime, + validation); obj.AddPairs("assetBufferSize", assetBufferSize, "assetCount", assetCount); } @@ -92,10 +90,10 @@ namespace mtconnect::printer { const uint64_t instanceId, const unsigned int bufferSize, const uint64_t nextSequence, const uint64_t firstSequence, const uint64_t lastSequence, const string &schemaVersion, - const string modelChangeTime, - const bool validation) + const string modelChangeTime, const bool validation) { - header(obj, version, hostname, instanceId, bufferSize, schemaVersion, modelChangeTime, validation); + header(obj, version, hostname, instanceId, bufferSize, schemaVersion, modelChangeTime, + validation); obj.AddPairs("nextSequence", nextSequence, "lastSequence", lastSequence, "firstSequence", firstSequence); } @@ -213,8 +211,7 @@ namespace mtconnect::printer { { AutoJsonObject obj(writer, "Header"); probeAssetHeader(obj, m_version, m_senderName, instanceId, 0, bufferSize, assetCount, - *m_schemaVersion, m_modelChangeTime, - m_validation); + *m_schemaVersion, m_modelChangeTime, m_validation); } { obj.Key("Assets"); @@ -412,8 +409,7 @@ namespace mtconnect::printer { { AutoJsonObject obj(writer, "Header"); streamHeader(obj, m_version, m_senderName, instanceId, bufferSize, nextSeq, firstSeq, - lastSeq, *m_schemaVersion, m_modelChangeTime, - m_validation); + lastSeq, *m_schemaVersion, m_modelChangeTime, m_validation); } { diff --git a/src/mtconnect/printer/printer.hpp b/src/mtconnect/printer/printer.hpp index 4e39cb0a..52943c17 100644 --- a/src/mtconnect/printer/printer.hpp +++ b/src/mtconnect/printer/printer.hpp @@ -49,7 +49,9 @@ namespace mtconnect { public: /// @brief construct a printer /// @param pretty `true` if content should be pretty printed - Printer(bool pretty = false, bool validation = false) : m_pretty(pretty), m_validation(validation) {} + Printer(bool pretty = false, bool validation = false) + : m_pretty(pretty), m_validation(validation) + {} virtual ~Printer() = default; /// @brief Generate an MTConnect Error document @@ -145,19 +147,18 @@ namespace mtconnect { const_cast(this)->m_schemaVersion.emplace(ver); } } - + /// @brief Get validation header flag state /// @returns validation state bool getValidation() const { return m_validation; } - + /// @brief sets validation state /// @param validation the validation state void setValidation(bool v) { m_validation = v; } - protected: - bool m_pretty; //< Turns pretty printing on - bool m_validation; //< Sets validation flag in header + bool m_pretty; //< Turns pretty printing on + bool m_validation; //< Sets validation flag in header std::string m_modelChangeTime; std::optional m_schemaVersion; std::string m_senderName {"localhost"}; diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 6448959e..56e05947 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -100,7 +100,10 @@ namespace mtconnect::printer { xmlBufferPtr m_buf; }; - XmlPrinter::XmlPrinter(bool pretty, bool validation) : Printer(pretty, validation) { NAMED_SCOPE("xml.printer"); } + XmlPrinter::XmlPrinter(bool pretty, bool validation) : Printer(pretty, validation) + { + NAMED_SCOPE("xml.printer"); + } void XmlPrinter::addDevicesNamespace(const std::string &urn, const std::string &location, const std::string &prefix) @@ -636,7 +639,7 @@ namespace mtconnect::printer { addAttribute(writer, "sender", m_senderName); addAttribute(writer, "instanceId", to_string(instanceId)); - + if (m_validation) addAttribute(writer, "validation", "true"s); diff --git a/src/mtconnect/source/adapter/adapter_pipeline.cpp b/src/mtconnect/source/adapter/adapter_pipeline.cpp index 615b4741..6432a6cf 100644 --- a/src/mtconnect/source/adapter/adapter_pipeline.cpp +++ b/src/mtconnect/source/adapter/adapter_pipeline.cpp @@ -139,7 +139,7 @@ namespace mtconnect { // Convert values if (IsOptionSet(m_options, configuration::ConversionRequired)) next = next->bind(make_shared()); - + // Validate Values if (IsOptionSet(m_options, configuration::Validation)) next = next->bind(make_shared(m_context)); diff --git a/src/mtconnect/source/loopback_source.cpp b/src/mtconnect/source/loopback_source.cpp index c28609b4..94248bc5 100644 --- a/src/mtconnect/source/loopback_source.cpp +++ b/src/mtconnect/source/loopback_source.cpp @@ -38,7 +38,7 @@ namespace mtconnect::source { void LoopbackPipeline::build(const ConfigOptions &options) { m_options = options; - + clear(); TransformPtr next = m_start; diff --git a/src/mtconnect/validation/observation_validations.hpp b/src/mtconnect/validation/observation_validations.hpp index d0bb08ab..1f7cb73e 100644 --- a/src/mtconnect/validation/observation_validations.hpp +++ b/src/mtconnect/validation/observation_validations.hpp @@ -1,139 +1,200 @@ -const Validation ControlledVocabularies { - {"ACTIVE_AXES", {}}, - {"ACTUATOR_STATE", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, - {"ALARM", {}}, - {"ASSET_CHANGED", {}}, - {"ASSET_REMOVED", {}}, - {"AVAILABILITY", {{"AVAILABLE", 0}, {"UNAVAILABLE", 0}}}, - {"AXIS_COUPLING", {{"TANDEM", 0}, {"SYNCHRONOUS", 0}, {"MASTER", 0}, {"SLAVE", 0}}}, - {"AXIS_FEEDRATE_OVERRIDE", {}}, - {"AXIS_INTERLOCK", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, - {"AXIS_STATE", {{"HOME", 0}, {"TRAVEL", 0}, {"PARKED", 0}, {"STOPPED", 0}}}, - {"BLOCK", {}}, - {"BLOCK_COUNT", {}}, - {"CHUCK_INTERLOCK", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, - {"CHUCK_STATE", {{"OPEN", 0}, {"CLOSED", 0}, {"UNLATCHED", 0}}}, - {"CODE", {}}, - {"COMPOSITION_STATE", {}}, - {"CONTROLLER_MODE", {{"AUTOMATIC", 0}, {"MANUAL", 0}, {"MANUAL_DATA_INPUT", 0}, {"SEMI_AUTOMATIC", 0}, {"EDIT", 0}, {"FEED_HOLD", SCHEMA_VERSION(1, 3)}}}, - {"CONTROLLER_MODE_OVERRIDE", {{"ON", 0}, {"OFF", 0}}}, - {"COUPLED_AXES", {}}, - {"DATE_CODE", {}}, - {"DEVICE_UUID", {}}, - {"DIRECTION", {{"CLOCKWISE", SCHEMA_VERSION(1, 4)}, {"COUNTER_CLOCKWISE", SCHEMA_VERSION(1, 4)}, {"POSITIVE", SCHEMA_VERSION(1, 4)}, {"NEGATIVE", SCHEMA_VERSION(1, 4)}}}, - {"DOOR_STATE", {{"OPEN", 0}, {"CLOSED", 0}, {"UNLATCHED", 0}}}, - {"EMERGENCY_STOP", {{"ARMED", 0}, {"TRIGGERED", 0}}}, - {"END_OF_BAR", {{"YES", 0}, {"NO", 0}}}, - {"EQUIPMENT_MODE", {{"ON", 0}, {"OFF", 0}}}, - {"EXECUTION", {{"READY", 0}, {"ACTIVE", 0}, {"INTERRUPTED", 0}, {"FEED_HOLD", 0}, {"STOPPED", 0}, {"OPTIONAL_STOP", 0}, {"PROGRAM_STOPPED", 0}, {"PROGRAM_COMPLETED", 0}, {"WAIT", 0}, {"PROGRAM_OPTIONAL_STOP", SCHEMA_VERSION(1, 4)}}}, - {"FUNCTIONAL_MODE", {{"PRODUCTION", 0}, {"SETUP", 0}, {"TEARDOWN", 0}, {"MAINTENANCE", 0}, {"PROCESS_DEVELOPMENT", 0}}}, - {"HARDNESS", {}}, - {"LINE", {}}, - {"LINE_LABEL", {}}, - {"LINE_NUMBER", {}}, - {"MATERIAL", {}}, - {"MATERIAL_LAYER", {}}, - {"MESSAGE", {}}, - {"OPERATOR_ID", {}}, - {"PALLET_ID", {}}, - {"PART_COUNT", {}}, - {"PART_DETECT", {{"PRESENT", 0}, {"NOT_PRESENT", 0}}}, - {"PART_ID", {}}, - {"PART_NUMBER", {}}, - {"PATH_FEEDRATE_OVERRIDE", {}}, - {"PATH_MODE", {{"INDEPENDENT", 0}, {"MASTER", 0}, {"SYNCHRONOUS", 0}, {"MIRROR", 0}}}, - {"POWER_STATE", {{"ON", 0}, {"OFF", 0}}}, - {"POWER_STATUS", {{"ON", SCHEMA_VERSION(1, 1)}, {"OFF", SCHEMA_VERSION(1, 1)}}}, - {"PROCESS_TIME", {}}, - {"PROGRAM", {}}, - {"PROGRAM_COMMENT", {}}, - {"PROGRAM_EDIT", {{"ACTIVE", 0}, {"READY", 0}, {"NOT_READY", 0}}}, - {"PROGRAM_EDIT_NAME", {}}, - {"PROGRAM_HEADER", {}}, - {"PROGRAM_LOCATION", {}}, - {"PROGRAM_LOCATION_TYPE", {{"LOCAL", 0}, {"EXTERNAL", 0}}}, - {"PROGRAM_NEST_LEVEL", {}}, - {"ROTARY_MODE", {{"SPINDLE", 0}, {"INDEX", 0}, {"CONTOUR", 0}}}, - {"ROTARY_VELOCITY_OVERRIDE", {}}, - {"SERIAL_NUMBER", {}}, - {"SPINDLE_INTERLOCK", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, - {"TOOL_ASSET_ID", {}}, - {"TOOL_GROUP", {}}, - {"TOOL_ID", {}}, - {"TOOL_NUMBER", {}}, - {"TOOL_OFFSET", {}}, - {"USER", {}}, - {"VARIABLE", {}}, - {"WAIT_STATE", {{"POWERING_UP", 0}, {"POWERING_DOWN", 0}, {"PART_LOAD", 0}, {"PART_UNLOAD", 0}, {"TOOL_LOAD", 0}, {"TOOL_UNLOAD", 0}, {"MATERIAL_LOAD", 0}, {"MATERIAL_UNLOAD", 0}, {"SECONDARY_PROCESS", 0}, {"PAUSING", 0}, {"RESUMING", 0}}}, - {"WIRE", {}}, - {"WORKHOLDING_ID", {}}, - {"WORK_OFFSET", {}}, - {"OPERATING_SYSTEM", {}}, - {"FIRMWARE", {}}, - {"APPLICATION", {}}, - {"LIBRARY", {}}, - {"HARDWARE", {}}, - {"NETWORK", {}}, - {"ROTATION", {}}, - {"TRANSLATION", {}}, - {"PROCESS_KIND_ID", {}}, - {"PART_STATUS", {{"PASS", 0}, {"FAIL", 0}}}, - {"ALARM_LIMIT", {}}, - {"PROCESS_AGGREGATE_ID", {}}, - {"PART_KIND_ID", {}}, - {"ADAPTER_URI", {}}, - {"DEVICE_REMOVED", {}}, - {"DEVICE_CHANGED", {}}, - {"SPECIFICATION_LIMIT", {}}, - {"CONNECTION_STATUS", {{"CLOSED", 0}, {"LISTEN", 0}, {"ESTABLISHED", 0}}}, - {"ADAPTER_SOFTWARE_VERSION", {}}, - {"SENSOR_ATTACHMENT", {}}, - {"CONTROL_LIMIT", {}}, - {"DEVICE_ADDED", {}}, - {"MTCONNECT_VERSION", {}}, - {"PROCESS_OCCURRENCE_ID", {}}, - {"PART_GROUP_ID", {}}, - {"PART_UNIQUE_ID", {}}, - {"ACTIVATION_COUNT", {}}, - {"DEACTIVATION_COUNT", {}}, - {"TRANSFER_COUNT", {}}, - {"LOAD_COUNT", {}}, - {"PART_PROCESSING_STATE", {{"NEEDS_PROCESSING", 0}, {"IN_PROCESS", 0}, {"PROCESSING_ENDED", 0}, {"PROCESSING_ENDED_COMPLETE", 0}, {"PROCESSING_ENDED_STOPPED", 0}, {"PROCESSING_ENDED_ABORTED", 0}, {"PROCESSING_ENDED_LOST", 0}, {"PROCESSING_ENDED_SKIPPED", 0}, {"PROCESSING_ENDED_REJECTED", 0}, {"WAITING_FOR_TRANSIT", 0}, {"IN_TRANSIT", 0}, {"TRANSIT_COMPLETE", 0}}}, - {"PROCESS_STATE", {{"INITIALIZING", 0}, {"READY", 0}, {"ACTIVE", 0}, {"COMPLETE", 0}, {"INTERRUPTED", 0}, {"ABORTED", 0}}}, - {"VALVE_STATE", {{"OPEN", 0}, {"OPENING", 0}, {"CLOSED", 0}, {"CLOSING", 0}}}, - {"LOCK_STATE", {{"LOCKED", 0}, {"UNLOCKED", 0}}}, - {"UNLOAD_COUNT", {}}, - {"CYCLE_COUNT", {}}, - {"OPERATING_MODE", {{"AUTOMATIC", 0}, {"MANUAL", 0}, {"SEMI_AUTOMATIC", 0}}}, - {"ASSET_COUNT", {}}, - {"MAINTENANCE_LIST", {}}, - {"FIXTURE_ID", {}}, - {"PART_COUNT_TYPE", {{"EACH", 0}, {"BATCH", 0}}}, - {"CLOCK_TIME", {}}, - {"NETWORK_PORT", {}}, - {"HOST_NAME", {}}, - {"LEAK_DETECT", {{"DETECTED", 0}, {"NOT_DETECTED", 0}}}, - {"BATTERY_STATE", {{"CHARGED", 0}, {"CHARGING", 0}, {"DISCHARGING", 0}, {"DISCHARGED", 0}}}, - {"FEATURE_PERSISTENT_ID", {}}, - {"SENSOR_STATE", {}}, - {"COMPONENT_DATA", {}}, - {"WORK_OFFSETS", {}}, - {"TOOL_OFFSETS", {}}, - {"FEATURE_MEASUREMENT", {}}, - {"ADAPTER_URI", {}}, - {"MEASUREMENT_TYPE", {}}, - {"MEASUREMENT_VALUE", {}}, - {"MEASUREMENT_UNITS", {}}, - {"ADAPTER_URI", {{"PASS", 0}, {"FAIL", 0}, {"REWORK", 0}, {"SYSTEM_ERROR", 0}, {"INDETERMINATE", 0}, {"NOT_ANALYZED", 0}, {"BASIC_OR_THEORETIC_EXACT_DIMENSION", 0}, {"UNDEFINED", 0}}}, - {"UNCERTAINTY_TYPE", {{"COMBINED", 0}, {"MEAN", 0}}}, - {"UNCERTAINTY", {}}, - {"ALARM_LIMIT", {}}, - {"CONTROL_LIMIT", {}}, - {"SPECIFICATION_LIMIT", {}}, - {"TOOL_CUTTING_ITEM", {}}, - {"LOCATION_ADDRESS", {}}, - {"ACTIVE_POWER_SOURCE", {}}, - {"LOCATION_NARRATIVE", {}}, - {"THICKNESS", {}}, - {"LOCATION_SPATIAL_GEOGRAPHIC", {}} - }; +Validation ControlledVocabularies { + {"ActiveAxes", {}}, + {"ActuatorState", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, + {"Alarm", {}}, + {"AssetChanged", {}}, + {"AssetRemoved", {}}, + {"Availability", {{"AVAILABLE", 0}, {"UNAVAILABLE", 0}}}, + {"AxisCoupling", {{"TANDEM", 0}, {"SYNCHRONOUS", 0}, {"MASTER", 0}, {"SLAVE", 0}}}, + {"AxisFeedrateOverride", {}}, + {"AxisInterlock", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, + {"AxisState", {{"HOME", 0}, {"TRAVEL", 0}, {"PARKED", 0}, {"STOPPED", 0}}}, + {"Block", {}}, + {"BlockCount", {}}, + {"ChuckInterlock", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, + {"ChuckState", {{"OPEN", 0}, {"CLOSED", 0}, {"UNLATCHED", 0}}}, + {"Code", {}}, + {"CompositionState", {}}, + {"ControllerMode", + {{"AUTOMATIC", 0}, + {"MANUAL", 0}, + {"MANUAL_DATA_INPUT", 0}, + {"SEMI_AUTOMATIC", 0}, + {"EDIT", 0}, + {"FEED_HOLD", SCHEMA_VERSION(1, 3)}}}, + {"ControllerModeOverride", {{"ON", 0}, {"OFF", 0}}}, + {"CoupledAxes", {}}, + {"DateCode", {}}, + {"DeviceUuid", {}}, + {"Direction", + {{"CLOCKWISE", SCHEMA_VERSION(1, 4)}, + {"COUNTER_CLOCKWISE", SCHEMA_VERSION(1, 4)}, + {"POSITIVE", SCHEMA_VERSION(1, 4)}, + {"NEGATIVE", SCHEMA_VERSION(1, 4)}}}, + {"DoorState", {{"OPEN", 0}, {"CLOSED", 0}, {"UNLATCHED", 0}}}, + {"EmergencyStop", {{"ARMED", 0}, {"TRIGGERED", 0}}}, + {"EndOfBar", {{"YES", 0}, {"NO", 0}}}, + {"EquipmentMode", {{"ON", 0}, {"OFF", 0}}}, + {"Execution", + {{"READY", 0}, + {"ACTIVE", 0}, + {"INTERRUPTED", 0}, + {"FEED_HOLD", 0}, + {"STOPPED", 0}, + {"OPTIONAL_STOP", 0}, + {"PROGRAM_STOPPED", 0}, + {"PROGRAM_COMPLETED", 0}, + {"WAIT", 0}, + {"PROGRAM_OPTIONAL_STOP", SCHEMA_VERSION(1, 4)}}}, + {"FunctionalMode", + {{"PRODUCTION", 0}, + {"SETUP", 0}, + {"TEARDOWN", 0}, + {"MAINTENANCE", 0}, + {"PROCESS_DEVELOPMENT", 0}}}, + {"Hardness", {}}, + {"Line", {}}, + {"LineLabel", {}}, + {"LineNumber", {}}, + {"Material", {}}, + {"MaterialLayer", {}}, + {"Message", {}}, + {"OperatorId", {}}, + {"PalletId", {}}, + {"PartCount", {}}, + {"PartDetect", {{"PRESENT", 0}, {"NOT_PRESENT", 0}}}, + {"PartId", {}}, + {"PartNumber", {}}, + {"PathFeedrateOverride", {}}, + {"PathMode", {{"INDEPENDENT", 0}, {"MASTER", 0}, {"SYNCHRONOUS", 0}, {"MIRROR", 0}}}, + {"PowerState", {{"ON", 0}, {"OFF", 0}}}, + {"PowerStatus", {{"ON", SCHEMA_VERSION(1, 1)}, {"OFF", SCHEMA_VERSION(1, 1)}}}, + {"ProcessTime", {}}, + {"Program", {}}, + {"ProgramComment", {}}, + {"ProgramEdit", {{"ACTIVE", 0}, {"READY", 0}, {"NOT_READY", 0}}}, + {"ProgramEditName", {}}, + {"ProgramHeader", {}}, + {"ProgramLocation", {}}, + {"ProgramLocationType", {{"LOCAL", 0}, {"EXTERNAL", 0}}}, + {"ProgramNestLevel", {}}, + {"RotaryMode", {{"SPINDLE", 0}, {"INDEX", 0}, {"CONTOUR", 0}}}, + {"RotaryVelocityOverride", {}}, + {"SerialNumber", {}}, + {"SpindleInterlock", {{"ACTIVE", 0}, {"INACTIVE", 0}}}, + {"ToolAssetId", {}}, + {"ToolGroup", {}}, + {"ToolId", {}}, + {"ToolNumber", {}}, + {"ToolOffset", {}}, + {"User", {}}, + {"Variable", {}}, + {"WaitState", + {{"POWERING_UP", 0}, + {"POWERING_DOWN", 0}, + {"PART_LOAD", 0}, + {"PART_UNLOAD", 0}, + {"TOOL_LOAD", 0}, + {"TOOL_UNLOAD", 0}, + {"MATERIAL_LOAD", 0}, + {"MATERIAL_UNLOAD", 0}, + {"SECONDARY_PROCESS", 0}, + {"PAUSING", 0}, + {"RESUMING", 0}}}, + {"Wire", {}}, + {"WorkholdingId", {}}, + {"WorkOffset", {}}, + {"OperatingSystem", {}}, + {"Firmware", {}}, + {"Application", {}}, + {"Library", {}}, + {"Hardware", {}}, + {"Network", {}}, + {"Rotation", {}}, + {"Translation", {}}, + {"ProcessKindId", {}}, + {"PartStatus", {{"PASS", 0}, {"FAIL", 0}}}, + {"AlarmLimit", {}}, + {"ProcessAggregateId", {}}, + {"PartKindId", {}}, + {"AdapterURI", {}}, + {"DeviceRemoved", {}}, + {"DeviceChanged", {}}, + {"SpecificationLimit", {}}, + {"ConnectionStatus", {{"CLOSED", 0}, {"LISTEN", 0}, {"ESTABLISHED", 0}}}, + {"AdapterSoftwareVersion", {}}, + {"SensorAttachment", {}}, + {"ControlLimit", {}}, + {"DeviceAdded", {}}, + {"MTConnectVersion", {}}, + {"ProcessOccurrenceId", {}}, + {"PartGroupId", {}}, + {"PartUniqueId", {}}, + {"ActivationCount", {}}, + {"DeactivationCount", {}}, + {"TransferCount", {}}, + {"LoadCount", {}}, + {"PartProcessingState", + {{"NEEDS_PROCESSING", 0}, + {"IN_PROCESS", 0}, + {"PROCESSING_ENDED", 0}, + {"PROCESSING_ENDED_COMPLETE", 0}, + {"PROCESSING_ENDED_STOPPED", 0}, + {"PROCESSING_ENDED_ABORTED", 0}, + {"PROCESSING_ENDED_LOST", 0}, + {"PROCESSING_ENDED_SKIPPED", 0}, + {"PROCESSING_ENDED_REJECTED", 0}, + {"WAITING_FOR_TRANSIT", 0}, + {"IN_TRANSIT", 0}, + {"TRANSIT_COMPLETE", 0}}}, + {"ProcessState", + {{"INITIALIZING", 0}, + {"READY", 0}, + {"ACTIVE", 0}, + {"COMPLETE", 0}, + {"INTERRUPTED", 0}, + {"ABORTED", 0}}}, + {"ValveState", {{"OPEN", 0}, {"OPENING", 0}, {"CLOSED", 0}, {"CLOSING", 0}}}, + {"LockState", {{"LOCKED", 0}, {"UNLOCKED", 0}}}, + {"UnloadCount", {}}, + {"CycleCount", {}}, + {"OperatingMode", {{"AUTOMATIC", 0}, {"MANUAL", 0}, {"SEMI_AUTOMATIC", 0}}}, + {"AssetCount", {}}, + {"MaintenanceList", {}}, + {"FixtureId", {}}, + {"PartCountType", {{"EACH", 0}, {"BATCH", 0}}}, + {"ClockTime", {}}, + {"NetworkPort", {}}, + {"HostName", {}}, + {"LeakDetect", {{"DETECTED", 0}, {"NOT_DETECTED", 0}}}, + {"BatteryState", {{"CHARGED", 0}, {"CHARGING", 0}, {"DISCHARGING", 0}, {"DISCHARGED", 0}}}, + {"FeaturePersisitentId", {}}, + {"SensorState", {}}, + {"ComponentData", {}}, + {"WorkOffsets", {}}, + {"ToolOffsets", {}}, + {"FeatureMeasurement", {}}, + {"CharacteristicPersistentId", {}}, + {"MeasurementType", {}}, + {"MeasurementValue", {}}, + {"MeasurementUnits", {}}, + {"CharacteristicStatus", + {{"PASS", 0}, + {"FAIL", 0}, + {"REWORK", 0}, + {"SYSTEM_ERROR", 0}, + {"INDETERMINATE", 0}, + {"NOT_ANALYZED", 0}, + {"BASIC_OR_THEORETIC_EXACT_DIMENSION", 0}, + {"UNDEFINED", 0}}}, + {"UncertaintyType", {{"COMBINED", 0}, {"MEAN", 0}}}, + {"Uncertainty", {}}, + {"AlarmLimits", {}}, + {"ControlLimits", {}}, + {"SpecificationLimits", {}}, + {"ToolCuttingItem", {}}, + {"LocationAddress", {}}, + {"ActivePowerSource", {}}, + {"LocationNarrative", {}}, + {"Thickness", {}}, + {"LocationSpatialGeographic", {}}}; diff --git a/src/mtconnect/validation/observations.hpp b/src/mtconnect/validation/observations.hpp index a9d8c005..0eebdbed 100644 --- a/src/mtconnect/validation/observations.hpp +++ b/src/mtconnect/validation/observations.hpp @@ -17,13 +17,31 @@ #pragma once -#include "../utilities.hpp" - -#include #include +#include -namespace mtconnect::validation::observations { - using Validation = std::unordered_map>; +#include "../utilities.hpp" + +namespace mtconnect { - const extern Validation ControlledVocabularies; -} + /// @brief MTConnect validation containers + namespace validation { + + /// @brief Observation validation containers + namespace observations { + + /// @brief Validation type for observations + using Validation = std::unordered_map>; + + /// @brief Global Validations for Event Observation's Controlled Vocabularies + /// + /// The map is as follows: + /// * Event name --> + /// * Map of valid values. Empty map if not controlled. + /// * Map has is a pair of value to + /// * 0 if not deprecated + /// * SCHEMA_VERSION if deprecated + extern Validation ControlledVocabularies; + } // namespace observations + } // namespace validation +} // namespace mtconnect diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index d7b673f8..82bf2ba9 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -548,7 +548,7 @@ TEST_F(AgentTest, SampleAtNextSeq) } } -TEST_F(AgentTest, SampleCount) +TEST_F(AgentTest, should_give_correct_number_of_samples_with_count) { QueryMap query; addAdapter(); @@ -584,7 +584,7 @@ TEST_F(AgentTest, SampleCount) } } -TEST_F(AgentTest, SampleLastCount) +TEST_F(AgentTest, should_give_correct_number_of_samples_with_negative_count) { QueryMap query; addAdapter(); @@ -620,7 +620,7 @@ TEST_F(AgentTest, SampleLastCount) } } -TEST_F(AgentTest, SampleToParameter) +TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) { QueryMap query; addAdapter(); @@ -683,7 +683,7 @@ TEST_F(AgentTest, SampleToParameter) // to > from } -TEST_F(AgentTest, EmptyStream) +TEST_F(AgentTest, should_give_empty_stream_with_no_new_samples) { { PARSE_XML_RESPONSE("/current"); @@ -741,7 +741,7 @@ TEST_F(AgentTest, AddToBuffer) } } -TEST_F(AgentTest, SequenceNumberRollover) +TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) { #ifndef WIN32 QueryMap query; @@ -3075,3 +3075,66 @@ TEST_F(AgentTest, should_set_sender_from_config_in_XML_header) ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); } } + +TEST_F(AgentTest, should_set_validation_flag_in_header_when_version_2_5_validation_on) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.5", 4, false, + true, {{configuration::Validation, true}}); + ASSERT_TRUE(agent); + { + PARSE_XML_RESPONSE("/probe"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); + } + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); + } + + { + PARSE_XML_RESPONSE("/sample"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); + } +} + +TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_5) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.4", 4, false, + true, {{configuration::Validation, true}}); + ASSERT_TRUE(agent); + { + PARSE_XML_RESPONSE("/probe"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } + + { + PARSE_XML_RESPONSE("/sample"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } +} + +TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_validation_is_false) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.5", 4, false, + true, {{configuration::Validation, false}}); + ASSERT_TRUE(agent); + { + PARSE_XML_RESPONSE("/probe"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } + + { + PARSE_XML_RESPONSE("/sample"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); + } +} diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index be6699ab..2c2cb3dc 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -15,17 +15,20 @@ // limitations under the License. // +/// @file +/// Observation validation tests + // Ensure that gtest is the first header otherwise Windows raises an error #include // Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) #include +#include "mtconnect/asset/asset.hpp" +#include "mtconnect/device_model/data_item/data_item.hpp" #include "mtconnect/entity/entity.hpp" #include "mtconnect/observation/observation.hpp" #include "mtconnect/pipeline/validator.hpp" -#include "mtconnect/asset/asset.hpp" -#include "mtconnect/device_model/data_item/data_item.hpp" using namespace mtconnect; using namespace mtconnect::pipeline; @@ -46,13 +49,11 @@ 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(int32_t schemaVersion) : m_schemaVersion(schemaVersion) {} DevicePtr findDevice(const std::string &) override { return nullptr; } DataItemPtr findDataItem(const std::string &device, const std::string &name) override { - return m_dataItems[name]; + return nullptr; } void eachDataItem(EachDataItem fun) override {} void deliverObservation(observation::ObservationPtr obs) override {} @@ -65,49 +66,51 @@ class MockPipelineContract : public PipelineContract void sourceFailed(const std::string &id) override {} const ObservationPtr checkDuplicate(const ObservationPtr &obs) const override { return obs; } - std::map &m_dataItems; int32_t m_schemaVersion; }; +/// @brief Validation tests for observations class ObservationValidationTest : public testing::Test { protected: void SetUp() override { m_context = make_shared(); + m_context->m_contract = make_unique(SCHEMA_VERSION(2, 5)); m_validator = make_shared(m_context); m_validator->bind(make_shared(TypeGuard(RUN))); m_time = Timestamp(date::sys_days(2021_y / jan / 19_d)) + 10h + 1min; ErrorList errors; - m_dataItem = DataItem::make( - {{"id", "exec"s}, {"category", "EVENT"s}, {"type", "EXECUTION"s}}, - errors); + m_dataItem = + DataItem::make({{"id", "exec"s}, {"category", "EVENT"s}, {"type", "EXECUTION"s}}, errors); } - void TearDown() override + void TearDown() override { m_validator.reset(); m_dataItem.reset(); m_context.reset(); } - + shared_ptr m_validator; shared_ptr m_context; DataItemPtr m_dataItem; Timestamp m_time; }; +/// @test Validate a valid value for Execution TEST_F(ObservationValidationTest, should_validate_value) { ErrorList errors; auto event = Observation::make(m_dataItem, {{"VALUE", "READY"s}}, m_time, errors); - + auto evt = (*m_validator)(std::move(event)); auto quality = evt->get("quality"); ASSERT_EQ("VALID", quality); } +/// @test Unavailable should always be valid TEST_F(ObservationValidationTest, unavailable_should_be_valid) { ErrorList errors; @@ -117,27 +120,101 @@ TEST_F(ObservationValidationTest, unavailable_should_be_valid) ASSERT_EQ("VALID", quality); } +/// @test Invalid values should be marked as invalid TEST_F(ObservationValidationTest, should_detect_invalid_value) { ErrorList errors; auto event = Observation::make(m_dataItem, {{"VALUE", "FLABOR"s}}, m_time, errors); - + auto evt = (*m_validator)(std::move(event)); auto quality = evt->get("quality"); ASSERT_EQ("INVALID", quality); } +/// @test Unknown types should be unverifiable TEST_F(ObservationValidationTest, should_not_validate_unknown_type) { ErrorList errors; - m_dataItem = DataItem::make( - {{"id", "exec"s}, {"category", "EVENT"s}, {"type", "x:FLABOR"s}}, - errors); - + m_dataItem = + DataItem::make({{"id", "exec"s}, {"category", "EVENT"s}, {"type", "x:FLABOR"s}}, errors); + auto event = Observation::make(m_dataItem, {{"VALUE", "FLABOR"s}}, m_time, errors); - + auto evt = (*m_validator)(std::move(event)); auto quality = evt->get("quality"); ASSERT_EQ("UNVERIFIABLE", quality); } +/// @test Tag deprecated values +TEST_F(ObservationValidationTest, should_set_deprecated_flag_when_deprecated) +{ + ErrorList errors; + m_dataItem = + DataItem::make({{"id", "exec"s}, {"category", "EVENT"s}, {"type", "EXECUTION"s}}, errors); + + auto event = Observation::make(m_dataItem, {{"VALUE", "PROGRAM_OPTIONAL_STOP"s}}, m_time, errors); + + auto evt = (*m_validator)(std::move(event)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); + + auto dep = evt->get("deprecated"); + ASSERT_TRUE(dep); +} + +/// @test Only deprecate when the version is earlier than the current version +TEST_F(ObservationValidationTest, should_not_set_deprecated_flag_when_deprecated_version_greater) +{ + ErrorList errors; + m_dataItem = + DataItem::make({{"id", "exec"s}, {"category", "EVENT"s}, {"type", "EXECUTION"s}}, errors); + + auto contract = static_cast(m_context->m_contract.get()); + contract->m_schemaVersion = SCHEMA_VERSION(1, 3); + + auto event = Observation::make(m_dataItem, {{"VALUE", "PROGRAM_OPTIONAL_STOP"s}}, m_time, errors); + + auto evt = (*m_validator)(std::move(event)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); + + ASSERT_FALSE(evt->hasProperty("deprecated")); +} + +/// @test do not validate data sets +TEST_F(ObservationValidationTest, should_not_validate_data_sets) +{ + ErrorList errors; + m_dataItem = DataItem::make({{"id", "exec"s}, + {"category", "EVENT"s}, + {"type", "EXECUTION"s}, + {"representation", "DATA_SET"s}}, + errors); + ASSERT_TRUE(m_dataItem->isDataSet()); + + auto event = + Observation::make(m_dataItem, {{"VALUE", DataSet({{"field", "value"s}})}}, m_time, errors); + + auto evt = (*m_validator)(std::move(event)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); +} + +/// @test do not validate tables +TEST_F(ObservationValidationTest, should_not_validate_tables) +{ + ErrorList errors; + m_dataItem = DataItem::make({{"id", "exec"s}, + {"category", "EVENT"s}, + {"type", "EXECUTION"s}, + {"representation", "TABLE"s}}, + errors); + ASSERT_TRUE(m_dataItem->isDataSet()); + + auto event = + Observation::make(m_dataItem, {{"VALUE", DataSet({{"field", "value"s}})}}, m_time, errors); + + auto evt = (*m_validator)(std::move(event)); + auto quality = evt->get("quality"); + ASSERT_EQ("VALID", quality); +}