fix(mtconnect): serve an empty string-typed value as Good "" (#824)
Owner decision 2026-11-11 (Java parity): a genuinely empty observation on a String/Reference or unauthored item is Good "" instead of BadNoCommunication. Non-string items (numeric, bool, DateTime, arrays) keep BadNoCommunication - the Agent supplied no value; BadTypeMismatch would wrongly blame the authored type. The UNAVAILABLE sentinel is unchanged, and a structured observation with no text stays BadNotSupported (structured check now precedes the empty check). Parity matrix, quality-mapping section and drivers rule updated. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -76,7 +76,10 @@ internal sealed class MTConnectObservationIndex
|
||||
|
||||
private const uint Good = 0x00000000u;
|
||||
|
||||
/// <summary>The Agent reported <c>UNAVAILABLE</c>, or supplied no text at all.</summary>
|
||||
/// <summary>
|
||||
/// The Agent reported <c>UNAVAILABLE</c>, or supplied no text at all for a non-string item
|
||||
/// (an empty string-typed item is a Good <c>""</c> — lmxopcua#824).
|
||||
/// </summary>
|
||||
private const uint BadNoCommunication = 0x80310000u;
|
||||
|
||||
/// <summary>The tag is authored but the Agent has not yet reported it.</summary>
|
||||
@@ -443,10 +446,8 @@ internal sealed class MTConnectObservationIndex
|
||||
var serverTimestamp = Now;
|
||||
var sourceTimestamp = DateTime.SpecifyKind(observation.TimestampUtc, DateTimeKind.Utc);
|
||||
|
||||
// No text at all is the degenerate spelling of "no value": MTConnect defines UNAVAILABLE as
|
||||
// the only way to report absence, so an empty element is not an empty string value.
|
||||
if (string.IsNullOrWhiteSpace(observation.Value) ||
|
||||
string.Equals(observation.Value, UnavailableSentinel, StringComparison.Ordinal))
|
||||
// UNAVAILABLE is MTConnect's one explicit "no value" sentinel — always BadNoCommunication.
|
||||
if (string.Equals(observation.Value, UnavailableSentinel, StringComparison.Ordinal))
|
||||
{
|
||||
return new DataValueSnapshot(null, BadNoCommunication, sourceTimestamp, serverTimestamp);
|
||||
}
|
||||
@@ -456,7 +457,7 @@ internal sealed class MTConnectObservationIndex
|
||||
// Publishing that as a Good string would be noise an operator would trust. Checked before
|
||||
// the tag lookup because it is a fact about the wire shape, true whether or not the data
|
||||
// item is authored — and after the sentinel, so an unavailable data set still reports the
|
||||
// truthful no-comms.
|
||||
// truthful no-comms, but BEFORE the empty-text test (#824).
|
||||
if (observation.IsStructured)
|
||||
{
|
||||
return new DataValueSnapshot(null, BadNotSupported, sourceTimestamp, serverTimestamp);
|
||||
@@ -464,6 +465,24 @@ internal sealed class MTConnectObservationIndex
|
||||
|
||||
_tags.TryGetValue(observation.DataItemId, out var tag);
|
||||
|
||||
// A genuinely EMPTY element (no text, or whitespace the parser trimmed away) is NOT the
|
||||
// sentinel. Owner decision on lmxopcua#824 (Java parity): on a string-typed item it is a
|
||||
// Good empty string — an Agent can legitimately report a blank Program/Message/Block.
|
||||
// An unauthored item coerces as String (below), so it is served the same way.
|
||||
// On any other type (numeric, Boolean, DateTime, any array) "" cannot be a value, so it
|
||||
// stays Bad — and stays BadNoCommunication rather than BadTypeMismatch on purpose:
|
||||
// the Agent supplied no value at all (the same meaning as UNAVAILABLE, just spelled
|
||||
// degenerately), whereas BadTypeMismatch tells the operator the Agent sent text of the
|
||||
// wrong type and points them at the tag's authored data type, which is not the fault.
|
||||
// Checked AFTER the structured test above, so element-children with no text stay
|
||||
// BadNotSupported rather than becoming a Good "".
|
||||
if (string.IsNullOrWhiteSpace(observation.Value))
|
||||
{
|
||||
return tag is null or { IsArray: false, DriverDataType: DriverDataType.String or DriverDataType.Reference }
|
||||
? new DataValueSnapshot(string.Empty, Good, sourceTimestamp, serverTimestamp)
|
||||
: new DataValueSnapshot(null, BadNoCommunication, sourceTimestamp, serverTimestamp);
|
||||
}
|
||||
|
||||
// TIME_SERIES vectors arrive as one whitespace-separated string. Checked AFTER the
|
||||
// sentinel so an unavailable array tag still reports the truthful no-comms.
|
||||
if (tag is { IsArray: true })
|
||||
|
||||
Reference in New Issue
Block a user