namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
///
/// Result of an Agent /probe request: the full device hierarchy the Agent exposes.
/// Produced by . This is the neutral shape both
/// the TrakHound-backed implementation and a hand-rolled XML fallback must produce — nothing
/// downstream of ever sees a TrakHound type or an
/// XElement.
///
/// Every device the Agent's probe response declares.
public sealed record MTConnectProbeModel(IReadOnlyList Devices);
///
/// Common shape shared by and :
/// each may directly own data items and/or nest further components, to arbitrary depth. Backs
/// the recursive walk.
///
public interface IMTConnectComponentContainer
{
/// Components nested directly under this device/component (may be empty).
IReadOnlyList Components { get; }
/// Data items declared directly on this device/component (may be empty).
IReadOnlyList DataItems { get; }
}
///
/// One MTConnect device from the Agent's probe response — the root of a nested
/// component/data-item tree.
///
/// The device's id attribute.
/// The device's name attribute, when the Agent supplies one.
/// Child components declared directly under this device.
/// Data items declared directly on this device (outside any component).
public sealed record MTConnectDevice(
string Id,
string? Name,
IReadOnlyList Components,
IReadOnlyList DataItems) : IMTConnectComponentContainer;
///
/// One MTConnect component (e.g. Axes, Controller, Path) from the Agent's
/// probe response. Components nest to arbitrary depth — a component may itself contain further
/// child components as well as data items.
///
/// The component's id attribute.
/// The component's name attribute, when the Agent supplies one.
/// Child components nested directly under this component.
/// Data items declared directly on this component.
public sealed record MTConnectComponent(
string Id,
string? Name,
IReadOnlyList Components,
IReadOnlyList DataItems) : IMTConnectComponentContainer;
///
/// One MTConnect DataItem declaration from the Agent's probe response. This is pure metadata —
/// it carries no observed value; values arrive separately via
/// /
/// and are correlated back to a data item by .
///
///
/// The DataItem's id attribute — globally unique within the Agent's probe response and
/// the correlation key used against .
///
/// The DataItem's name attribute, when the Agent supplies one.
/// The DataItem's category attribute — SAMPLE, EVENT, or CONDITION.
/// The DataItem's type attribute (e.g. POSITION, EXECUTION).
/// The DataItem's subType attribute (e.g. ACTUAL, COMMANDED), when present.
/// The DataItem's units attribute (e.g. MILLIMETER, REVOLUTION/MINUTE), when present.
///
/// The DataItem's representation attribute (e.g. TIME_SERIES, DISCRETE);
/// absent means the MTConnect default (VALUE).
///
///
/// The DataItem's sampleCount attribute — element count for a TIME_SERIES
/// representation. Present only on data items that carry one.
///
public sealed record MTConnectDataItem(
string Id,
string? Name,
string Category,
string Type,
string? SubType,
string? Units,
string? Representation,
int? SampleCount);
///
/// Result of an Agent /current request, or of a single multipart chunk from a
/// /sample stream. Produced by and
/// .
///
///
/// The Agent's current instance id (its MTConnectStreams header instanceId). Changes
/// whenever the Agent restarts or its underlying device model changes; the driver watches this
/// for change to raise rediscovery rather than trusting a stale observation snapshot.
///
///
/// The header nextSequence — the sequence number to resume a subsequent
/// call from.
///
///
/// The header firstSequence — the oldest sequence number still held in the Agent's
/// circular buffer. Load-bearing for sequence-gap detection: a caller that requested
/// from a sequence older than this chunk's has fallen out of
/// the Agent's buffer and must re-baseline via .
///
///
/// The observations carried in this snapshot/chunk, in Agent-supplied order. Never null;
/// empty when the chunk carries no new observations (e.g. a heartbeat).
///
public sealed record MTConnectStreamsResult(
long InstanceId,
long NextSequence,
long FirstSequence,
IReadOnlyList Observations);
///
/// One observed value for a single DataItem, as reported in a /current snapshot or a
/// /sample stream chunk. Deliberately dumb: is carried as the raw
/// string the Agent reported (including the literal UNAVAILABLE) with no interpretation
/// applied here — coercing UNAVAILABLE to a bad-quality status and converting the string
/// to the tag's DriverDataType are the observation index's job, not this layer's.
///
///
/// The reporting DataItem's id — correlates back to
/// from the probe model.
///
///
/// The observed value exactly as the Agent reported it, or the literal string
/// "UNAVAILABLE" when the Agent has no current value for the data item. Never
/// pre-parsed into a nullable or an enum at this layer.
///
///
/// The observation's Agent-reported timestamp, normalized to UTC (
/// is always ). Becomes the OPC UA variable's SourceTimestamp.
///
///
/// true when the Agent carried this observation's real content in child elements
/// rather than as text — an MTConnect 2.0 DATA_SET / TABLE observation, whose
/// content is a list of <Entry key="…"> elements.
///
/// Why the flag exists, and why only the parser can set it. is
/// the element's concatenated descendant text, so a two-entry data set reads as the single
/// token "12" — keys discarded, values run together. The observation index cannot
/// detect that after the fact: neither this record nor the tag definition carries the
/// DataItem's representation, and no value-shape heuristic can work, because
/// concatenated entries are indistinguishable from a legitimate space-bearing EVENT such as
/// a Message reading "Coolant level low". The one reliable discriminator —
/// that the observation element had element children — exists only while the XML is still
/// XML.
///
///
/// Deliberately a neutral fact about the wire shape, not a status: mapping it to a
/// quality code is the observation index's job (it codes these
/// BadNotSupported rather than publishing concatenated noise as Good). Defaults to
/// false, the shape of every ordinary scalar observation.
///
///
///
/// The observation's own sampleCount attribute — the number of values a TIME_SERIES
/// vector claims to carry (#627). The index checks the parsed vector against it. null when absent.
///
///
/// The observation's own sampleRate attribute (samples per second) on a TIME_SERIES
/// observation. Carried for the parser contract; not yet surfaced on the served node (see
/// docs/drivers/MTConnect.md). null when absent.
///
///
/// The parsed <Entry key="…"> children of a DATA_SET / TABLE observation (#650), in
/// document order, or null when the observation had none. A TABLE entry carries
/// . The observation index folds these into a JSON String — see
/// . stays true beside them: it is the
/// wire-shape fact, this is the content.
///
///
/// true when the observation carried a non-empty resetTriggered attribute — the Agent's statement that
/// the set was reset, so this observation replaces the accumulated set rather than merging into it.
///
///
/// true when the element is data-set shaped: it carries the count attribute every DATA_SET / TABLE
/// observation has (and no SAMPLE or ordinary EVENT does) or has <Entry> children. This — not
/// — is what lets the index serve an entry-less set as {} rather than as
/// an absent value; resetTriggered also appears on ordinary accumulating SAMPLEs and must not matter there.
///
///
/// The CONDITION-only detail (#626) — set exactly when the observation came from a
/// <Condition> container, null for every Sample/Event. Its presence is what marks an
/// observation as a condition for the alarm path; still carries the state word.
///
public sealed record MTConnectObservation(
string DataItemId,
string Value,
DateTime TimestampUtc,
bool IsStructured = false,
int? SampleCount = null,
double? SampleRate = null,
MTConnectConditionDetail? Condition = null,
IReadOnlyList? Entries = null,
bool ResetTriggered = false,
bool IsDataSet = false);
/// One <Cell key="…"> of a TABLE row (#650).
public sealed record MTConnectDataSetCell(string Key, string Value);
///
/// One <Entry key="…"> of a DATA_SET / TABLE observation (#650).
///
/// The entry's key attribute, verbatim.
/// A DATA_SET entry's trimmed text; null for a TABLE row or a removed entry.
/// true when the entry carried removed="true": the key is deleted from the set.
/// A TABLE row's cells in document order; null for a DATA_SET entry.
public sealed record MTConnectDataSetEntry(
string Key,
string? Value,
bool Removed,
IReadOnlyList? Cells = null);
///
/// What an MTConnect CONDITION observation says beyond its state word (#626). Every field is the
/// Agent's own text, trimmed, or null when absent or blank — none is interpreted at this layer.
///
/// The element's text content: the operator-facing condition message.
/// The nativeCode attribute — the controller's own alarm number/code.
/// The nativeSeverity attribute — the controller's own severity, verbatim.
/// The qualifier attribute (e.g. HIGH, LOW).
/// The element's type attribute (e.g. SYSTEM, TEMPERATURE).
public sealed record MTConnectConditionDetail(
string? Text,
string? NativeCode,
string? NativeSeverity,
string? Qualifier,
string? ConditionType);
///
/// Recursive helpers over the device/component tree.
///
public static class MTConnectComponentTreeExtensions
{
///
/// Enumerates every data item owned by this device or component, plus every data item owned
/// by any component nested beneath it, to arbitrary depth. Order is depth-first: a node's
/// own data items first, then each child component's data items in turn.
///
/// The device or component to walk.
public static IEnumerable AllDataItems(this IMTConnectComponentContainer container)
{
foreach (var dataItem in container.DataItems)
{
yield return dataItem;
}
foreach (var child in container.Components)
{
foreach (var dataItem in child.AllDataItems())
{
yield return dataItem;
}
}
}
}