diff --git a/specification/src/main/asciidoc/chapters/Sparkplug_6_Payloads.adoc b/specification/src/main/asciidoc/chapters/Sparkplug_6_Payloads.adoc index 7ee0220..3238dbe 100644 --- a/specification/src/main/asciidoc/chapters/Sparkplug_6_Payloads.adoc +++ b/specification/src/main/asciidoc/chapters/Sparkplug_6_Payloads.adoc @@ -311,7 +311,7 @@ message Payload { optional uint64 alias = 2; // Metric alias - tied to name on BIRTH and included in all later DATA messages optional uint64 timestamp = 3; // Metric value change time - the time this metric's value last changed - if omitted, the overall payload timestamp is assumed as the metric value change time optional DataType datatype = 4; // DataType of the metric/tag value - optional int32 quality = 5; // Quality code of the metric/tag value + optional uint32 quality = 5; // Quality code of the metric/tag value - severity in bits 30-31 optional string quality_context = 6; // The optional quality context of the metric/tag value optional string source = 7; // Source for CMD messages if unique to the Metric (typically for forwarded writes in CMD messages) optional bool is_historical = 8; // If this is historical data and should not update real time tag @@ -708,21 +708,12 @@ datatype MUST be included with each metric definition in NBIRTH and DBIRTH messa ** [tck-testable tck-id-payloads-metric-datatype-not-req]#[yellow-background]*[tck-id-payloads-metric-datatype-not-req] The datatype SHOULD NOT be included with metric definitions in NDATA, NCMD, DDATA, and DCMD messages.*# * *quality* -** This defines the quality of the value associated with the metric. This property is optional and -is only required if the quality of the metric is not GOOD. If the quality is not present, it is -implicitly GOOD. -** There are three defined qualities. These are defined below with their associated meanings. -*** *0* -**** BAD -*** *192* -**** GOOD -*** *500* -**** STALE -** [tck-testable tck-id-payloads-quality-value]#[yellow-background]*[tck-id-payloads-quality-value] The -value of quality MUST be an int32.*# +** This defines the quality of the value associated with the metric. It is defined in the +link:#payloads_c_quality[Quality] section. This property is optional and is only required if the +quality of the metric is not Good. If the quality is not present, it is implicitly Good. * *quality_context* ** This is an optional string value used to provide additional context to the quality code of the -metric. +metric. It is defined in the link:#payloads_c_quality[Quality] section. * *source* ** This is the source field which represents the Sparkplug Host Application ID of the publisher for NCMD, DCMD, and REBIRTH messages if it differs from the overall payload source value. This may be @@ -767,6 +758,102 @@ can be found here: https://developers.google.com/protocol-buffers/docs/proto#sca *** Sparkplug _Template_ **** Defined link:#payloads_c_template[here]. +[[payloads_c_quality]] +==== Quality + +The quality of a Metric's value is carried in the quality field of that Metric, with optional +diagnostic text in quality_context. A quality code is a 32-bit unsigned value whose two most +significant bits carry a _severity_ and whose remaining bits carry a _subcode_ identifying the +specific condition. The severity is what a consuming application acts on; the subcode tells it why. + +Sparkplug uses the quality code numbering defined by OPC UA for its StatusCode type. Adopting an +existing numbering rather than defining a new one means that the large number of Sparkplug Edge +Nodes which acquire data from OPC UA servers, or which are themselves fronting an OPC UA address +space, can report a value's quality without translating it. + +[[payloads_c_quality_severity]] +===== Severity + +* [tck-testable tck-id-payloads-quality-severity-valid]#[yellow-background]*[tck-id-payloads-quality-severity-valid] The +two most significant bits of a quality code, bits 30 and 31, MUST be one of the following values. +The value 11 is reserved and MUST NOT be published.*# + +[cols="1,2,3",options="header"] +|=== +|*Bits 30-31* |*Severity* |*Meaning* +|00 |Good |The value is usable. +|01 |Uncertain |The value is usable but its accuracy or currency is in doubt. +|10 |Bad |The value is not usable. +|11 |(reserved) |MUST NOT be published. +|=== + +* [tck-not-testable tck-id-payloads-quality-severity-authoritative]#[yellow-background]*[tck-id-payloads-quality-severity-authoritative] A consuming application MUST determine whether a Metric's +value is usable from the severity alone, and MUST NOT require recognition of the subcode in order to +do so.*# +** Non-normative comment: this is what allows an Edge Node to report a specific condition without +every consuming application needing to know that condition. A consuming application that does not +recognize a subcode still handles the value correctly, because the severity is unambiguous. It is +also why this specification names only the quality codes below rather than enumerating every code +OPC UA defines. +* [tck-not-testable tck-id-payloads-quality-absent-good]#[yellow-background]*[tck-id-payloads-quality-absent-good] A quality code that is not present MUST be interpreted as +Good.*# +** Non-normative comment: the Good severity is the value 0, so an absent quality field and a quality +field containing Good carry the same meaning. An Edge Node therefore only needs to include the field +when the quality of a value is something other than Good. + +[[payloads_c_quality_codes]] +===== Quality Codes + +* [tck-not-testable tck-id-payloads-quality-recognized-codes]#[yellow-background]*[tck-id-payloads-quality-recognized-codes] A consuming application MUST recognize the following quality +codes. An Edge Node MAY publish any quality code defined by OPC UA for its StatusCode type, and a +consuming application MUST handle a code that is not listed here according to its severity.*# + +[cols="2,1,1,3",options="header"] +|=== +|*Quality Code* |*Value* |*Severity* |*Meaning* +|Good |0x00000000 |Good |The value is usable. +|Uncertain |0x40000000 |Uncertain |The value is usable but in doubt, for an unspecified reason. +|UncertainLastUsableValue |0x40900000 |Uncertain |The value is the last one known to be usable. This +is the quality code that denotes a STALE value. +|UncertainInitialValue |0x40920000 |Uncertain |The value is an initial value and has not yet been +acquired from the source. +|UncertainSensorNotAccurate |0x40930000 |Uncertain |The value is outside the operating range of the +sensor that produced it. +|Bad |0x80000000 |Bad |The value is not usable, for an unspecified reason. +|BadNoCommunication |0x80310000 |Bad |Communication with the source of the value is not established. +|BadNotConnected |0x808A0000 |Bad |The source of the value is not connected. +|BadDeviceFailure |0x808B0000 |Bad |The device that produces the value has failed. +|BadSensorFailure |0x808C0000 |Bad |The sensor that produces the value has failed. +|BadOutOfService |0x808D0000 |Bad |The source of the value is out of service. +|=== + +* [tck-not-testable tck-id-payloads-quality-stale-code]#[yellow-background]*[tck-id-payloads-quality-stale-code] A value that this specification describes as STALE MUST be +denoted by the UncertainLastUsableValue quality code.*# +** Non-normative comment: a STALE value is one that was accurate when it was acquired but can no +longer be confirmed as current, which is why its severity is Uncertain rather than Bad. The +circumstances in which a Host Application marks a value STALE are described in the +link:#operational_behavior[Operational Behavior] chapter. + +Non-normative comment: earlier versions of this specification defined three quality values of 0 for +BAD, 192 for GOOD, and 500 for STALE, which were derived from OPC DA rather than OPC UA. Those +values are not valid quality codes in this version. Note in particular that 0 denoted BAD in the +earlier scheme and denotes Good in this one. The two are never encountered together, because a +quality code is carried in a Metric field that exists only in this version of the specification, and +because an Edge Node publishes under a namespace that identifies which version of the specification +it implements. + +[[payloads_c_quality_context]] +===== Quality Context + +* [tck-not-testable tck-id-payloads-quality-context-not-required]#[yellow-background]*[tck-id-payloads-quality-context-not-required] The quality_context is an optional UTF-8 string carrying +diagnostic information about a quality code. A consuming application MUST NOT require the +quality_context in order to interpret a quality code, and MUST NOT rely on any particular content or +format within it.*# +** Non-normative comment: the quality_context is intended for a person diagnosing a problem, and +typically carries something like the identifier of the failed source or the text of an error +reported by an underlying protocol. The quality code itself carries everything a consuming +application needs in order to act. + [[payloads_c_metadata]] ==== MetaData diff --git a/tck/src/main/java/org/eclipse/sparkplug/tck/test/edge/SendComplexDataTest.java b/tck/src/main/java/org/eclipse/sparkplug/tck/test/edge/SendComplexDataTest.java index 6ce1e2a..610c6c7 100644 --- a/tck/src/main/java/org/eclipse/sparkplug/tck/test/edge/SendComplexDataTest.java +++ b/tck/src/main/java/org/eclipse/sparkplug/tck/test/edge/SendComplexDataTest.java @@ -38,8 +38,6 @@ import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_NAME_BIRTH_DATA_REQUIREMENT; import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_NAME_REQUIREMENT; import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE; -import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_QUALITY_VALUE; -import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_QUALITY_VALUE; import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_PROPERTYSET_VALUES_ARRAY_SIZE; import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_SEQUENCE_NUM_ALWAYS_INCLUDED; import static org.eclipse.sparkplug.tck.test.common.Requirements.ID_PAYLOADS_TEMPLATE_DATASET_VALUE; @@ -76,8 +74,6 @@ import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_NAME_BIRTH_DATA_REQUIREMENT; import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_NAME_REQUIREMENT; import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE; -import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_QUALITY_VALUE; -import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_QUALITY_VALUE; import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_PROPERTYSET_VALUES_ARRAY_SIZE; import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_SEQUENCE_NUM_ALWAYS_INCLUDED; import static org.eclipse.sparkplug.tck.test.common.Requirements.PAYLOADS_TEMPLATE_DATASET_VALUE; @@ -146,8 +142,7 @@ public class SendComplexDataTest extends TCKTest { ID_PAYLOADS_METRIC_DATATYPE_VALUE_TYPE, ID_PAYLOADS_METRIC_DATATYPE_VALUE, ID_PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE, ID_PAYLOADS_PROPERTYSET_VALUES_ARRAY_SIZE, ID_PAYLOADS_METRIC_PROPERTYVALUE_TYPE_TYPE, ID_PAYLOADS_METRIC_PROPERTYVALUE_TYPE_VALUE, - ID_PAYLOADS_METRIC_PROPERTYVALUE_TYPE_REQ, ID_PAYLOADS_QUALITY_VALUE, - ID_PAYLOADS_QUALITY_VALUE, ID_PAYLOADS_ALIAS_DATA_CMD_REQUIREMENT, + ID_PAYLOADS_METRIC_PROPERTYVALUE_TYPE_REQ, ID_PAYLOADS_ALIAS_DATA_CMD_REQUIREMENT, ID_PAYLOADS_METRIC_DATATYPE_NOT_REQ, ID_PAYLOADS_NAME_BIRTH_DATA_REQUIREMENT, ID_PAYLOADS_NAME_REQUIREMENT, ID_PAYLOADS_DATASET_COLUMN_SIZE, ID_PAYLOADS_DATASET_COLUMN_NUM_HEADERS, ID_PAYLOADS_DATASET_TYPES_DEF, ID_PAYLOADS_DATASET_TYPES_TYPE, ID_PAYLOADS_DATASET_TYPES_VALUE, ID_PAYLOADS_DATASET_TYPES_NUM, @@ -421,18 +416,11 @@ public void checkDatatypeValidType(final @NotNull PublishPacket packet) { @SpecAssertion( section = Sections.PAYLOADS_C_PROPERTYVALUE, id = ID_PAYLOADS_METRIC_PROPERTYVALUE_TYPE_REQ) - @SpecAssertion( - section = Sections.PAYLOADS_C_METRIC, - id = ID_PAYLOADS_QUALITY_VALUE) - @SpecAssertion( - section = Sections.PAYLOADS_C_METRIC, - id = ID_PAYLOADS_QUALITY_VALUE) public void checkPropertiesValidType(final @NotNull PublishPacket packet, String topic) { boolean isValid_KeyArraySize = true; boolean isValid_PropertyValueType = true; boolean isValid_PropertyValueTypeValue = true; boolean isValid_PropertyValueTypeReq = true; - boolean qualityCodeSettingIsUsed = false; logger.debug("Check Req: {} The datatype MUST be an unsigned 32-bit integer representing the datatype.", ID_PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE); @@ -466,9 +454,6 @@ public void checkPropertiesValidType(final @NotNull PublishPacket packet, String && m.getProperties().getValuesList().size() != m.getProperties().getKeysList().size()) { isValid_KeyArraySize = false; } - // execute always, but set only if one is true - qualityCodeSettingIsUsed = checkQualityCodeRequirement(m) || qualityCodeSettingIsUsed; - for (int i = 0; i < m.getProperties().getValuesCount(); i++) { final Payload.PropertyValue propertyValue = m.getProperties().getValues(i); if(!Utils.hasValidDatatype(propertyValue)){ @@ -482,14 +467,6 @@ public void checkPropertiesValidType(final @NotNull PublishPacket packet, String } } } - if (!qualityCodeSettingIsUsed) { - // option was not used -so test is than passed by default - otherwise the result is set in the subroutine - testResults.put(ID_PAYLOADS_QUALITY_VALUE, - setResult(true, PAYLOADS_QUALITY_VALUE)); - testResults.put(ID_PAYLOADS_QUALITY_VALUE, - setResult(true, PAYLOADS_QUALITY_VALUE)); - - } testResults.put(ID_PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE, setResult(isValid_KeyArraySize, PAYLOADS_PROPERTYSET_KEYS_ARRAY_SIZE)); testResults.put(ID_PAYLOADS_PROPERTYSET_VALUES_ARRAY_SIZE, @@ -501,31 +478,10 @@ public void checkPropertiesValidType(final @NotNull PublishPacket packet, String } - private boolean checkQualityCodeRequirement(Metric m) { - // optional key - but if it is used - it must fit to requirements - boolean qualityCodeSettingIsUsed = false; - for (int i = 0; i < m.getProperties().getValuesCount(); i++) { - final String key = m.getProperties().getKeys(i); - if (key.equals(PROPERTY_KEY_QUALITY)) { - final Payload.PropertyValue propertyValue = m.getProperties().getValues(i); - logger.debug( - "Check: Req: Property Value MUST be a value of 3 which represents a Signed 32-bit Integer."); - if (!(propertyValue.getType() == Payload.PropertyValue.ValueCase.LONG_VALUE.getNumber())) { - testResults.put(ID_PAYLOADS_QUALITY_VALUE, - setResult(false, PAYLOADS_QUALITY_VALUE)); - } - logger.debug( - "Check: Req: 'value' of the Property Value MUST be an int_value and be one of the valid quality codes of 0, 192, or 500."); - if (!(propertyValue.getLongValue() == 0 || propertyValue.getLongValue() == 192 - || propertyValue.getLongValue() == 500)) { - testResults.put(ID_PAYLOADS_QUALITY_VALUE, - setResult(false, PAYLOADS_QUALITY_VALUE)); - } - qualityCodeSettingIsUsed = true; - } - } - return qualityCodeSettingIsUsed; - } + // The assertion previously checked here, payloads-quality-value, tested quality carried as a + // PropertySet property named "Quality" with the values 0, 192 or 500. In this version of the + // specification quality is a Metric field carrying an OPC UA StatusCode, so this check no longer + // corresponds to anything the specification defines; see the Sparkplug v4 TCK rewrite. @SpecAssertion( section = Sections.PAYLOADS_C_METRIC,