# Description Correct ownership, endpoint validation, policy propagation, subscription delivery, file handling and generated-model behavior across the stack, tools and samples. Add focused regression tests and update the corresponding guides and sample documentation. ### Previously included corrections - Require an active, matching session to generate or access transient ResultTransfer file handles. Cover both sessionless generation and reads against a handle owned by an active session, using live session owners in the Machinery tests. - Keep physical filesystem operations within their configured mount and apply the same confinement checks throughout file operations. - Validate LDS semaphore paths consistently during server registration and registration-store maintenance. ### Individual issues addressed | # | Component | Issue and correction | |---|---|---| | 1 | AI jobs and transfers | Jobs and transfers did not consistently retain their creator's identity and session. Apply ownership checks to descendants, file handles, subscriptions and execution, and recheck deployment permissions before dispatch. | | 2 | Password-management client | Password-bearing calls could use signing-only channels or follow a replacement connection. Capture an encrypted transport generation and keep dispatch bound to it; preserve ordinary non-password edits. | | 3 | GDS and certificate-push clients | Connections omitted hostname verification. Validate the original configured DNS name or IP address before session activation. | | 4 | Certificate push | Private-key uploads could use signing-only calls. Require an encrypted, generation-bound dispatch while preserving certificate-only updates and restoring the caller's identity afterward. | | 5 | MQTT client | The .NET Framework custom-trust callback could discard TLS hostname, certificate-availability and usage errors. Retain those checks while supporting configured issuer trust. | | 6 | DTLS handshake | Certificate trust was not tied to the configured peer address. Match the certificate's DNS or IP identity to the intended endpoint. | | 7 | Replicated address spaces | Serialized nodes lost per-node and descendant permission metadata. Preserve role permissions, user-role permissions and access restrictions, including updates to existing replica nodes. | | 8 | MCP PubSub imports | Imports without groups skipped checks on the effective broker destination. Check the actual endpoint before resolving credentials or activating the connection. | | 9 | Reference-server accounts | A disabled username account could authenticate again with its previous roles. Reject disabled accounts during fresh authentication. | | 10 | Console reference client | The sample disabled hostname checks when connecting to an otherwise trusted peer. Retain the original requested host and validate it before activation. | | 11 | Capture replay and export | Replay and raw export could accept non-capture files or include key material. Require complete PCAP/PCAPNG containers and exclude key sidecars and decryption-secret blocks. | | 12 | PubSub adapter fallback | The fallback client configuration automatically accepted peer certificates. Apply normal trust and hostname validation before activation or reads. | | 13 | Kafka transport | Explicit TLS enablement was checked but did not reach both final Confluent configurations. Use the same effective protocol for validation, producers and consumers. | | 14 | PubSub actions | Configuration rebuilds could widen a connection-local action handler's scope. Preserve its original connection association and reject mismatched dispatch. | | 15 | PubSub readers | Reader-level message-protection settings were accepted but ignored. Apply the effective group and reader settings before accepting and publishing received values. | | 16 | PubSub key groups | Different `SecurityGroupId` values could share one connection-wide key context. Reject mixed groups on a connection and require separate connections for distinct groups. | | 17 | MCP session registrations | Session-name replacement or reuse could retain another registration's credential bindings. Bind access to the registration, caller and authenticated endpoint, and preserve that identity when borrowing sessions. | | 18 | Viewer HTTP host | Requests reached the viewer's OPC UA session without consistent Host, Origin and port checks. Validate the request destination and browser origin before using the session, while retaining supported local MCP clients. | | 19 | Vision file operations | File operations did not consistently apply protected-file checks, and remote handles lacked caller and connection context. Apply the shared checks and use host-side opaque handles bound to caller, target and generation. | | 20 | DTLS record integrity | The integrity-only MAC used a truncated sequence number. Authenticate the full reconstructed sequence number and epoch so records remain distinct across rollover. | | 21 | AMB client sample | Connection setup omitted hostname validation. Preserve the originally requested host and verify the peer identity before activation. | | 22 | Robot sample writes | The R2 `SpeedOverride` callback could target R1 state. Bind each callback to its own robot and apply that robot's write policy. | | 23 | AI result properties | Optional result children inherited remotely writable defaults. Preserve their declaration restrictions and keep server-produced results read-only. | | 24 | WSS peer-trust updates | Existing channels did not consistently respond to peer-trust changes. Revalidate complete chains for forward and reverse WSS channels and close only those that no longer validate. | | 25 | GDS revocation checks | Caller-provided certificates could cause issuer retrieval from embedded URLs. Resolve validation from configured local trust, issuers and CRLs without fetching caller-provided locations. | | 26 | GMS observations | Published correction observations retained writable model defaults. Reject remote writes while preserving publisher-side updates. | | 27 | Production-dataset monitoring | Subscription admission and delivery could use the reporter's context instead of each subscriber's permissions. Evaluate the subscriber at admission and on every delivery, including cancellation and disposal paths. | | 28 | WoT MQTT destinations | Hostless MQTT forms could imply a localhost destination before loopback checks. Reject them before credential lookup or connection attempts and validate the effective endpoint. | | 29 | XRegistry opaque reads | Content-ID reads and notifications did not consistently honor encrypted-read requirements or the currently accepted transport. Recheck the bound endpoint and reject transport downgrades before rebinding, including when protected values are already queued. | | 30 | PubSub capture dissection | Key loading did not consistently check the configured capture-root boundary. Check each actual key path immediately before opening it. | | 31 | PubSub publication queues | Imported WriterGroup queues were not fully checked against publish-topic grants. Check the destination actually used by each group. | | 32 | MCP PubSub mutations | A caller-supplied mode could select subscribe grants for an operation that publishes. Derive the required direction and parent context from the actual mutation. | | 33 | Generated write masks | Model import and generation could widen an explicit `UserWriteMask`. Preserve it through import, copy, merge, inheritance and dependency metadata. | | 34 | Generated method flags | An explicit `UserExecutable=false` could be lost. Preserve its specified state and resulting method behavior through the same model paths. | | 35 | Generated role identifiers | Unresolved role identifiers could cause permission entries to be omitted. Preserve namespace-qualified role IDs and report a diagnostic when a policy cannot be represented. | | 36 | Aggregation-server defaults | The `localhost` default did not reliably constrain listener binding. Use numeric loopback for the default endpoint and command-line fallback. | | 37 | Certificate-validation cache | Cached results could skip current leaf or issuer validity checks. Recheck validity windows on cache hits and keep hostname validation independent of cached chain trust. | | 38 | Software-update folders | Revision names and marker contents containing dot segments could select paths outside the software root. Normalize and check every derived path against that root. | | 39 | USDA arrays | Array elements were not encoded for their string or asset context. Escape them as data and preserve valid composition and round-trip behavior. | | 40 | Adapter hot reload | A changed dataset definition with the same name could keep old fields, subscriptions or cached values active. Compare complete immutable definitions and retire old state before replacement. | | 41 | Adapter index ranges | Subscription publication could ignore `IndexRange` and return the complete value. Preserve the requested projection and its status and timestamps; reject unsupported range providers. | | 42 | PubSub metadata and targets | Metadata from one connection could change another connection's target mapping. Scope metadata to its connection and provenance, and resolve targets using actual typed field IDs rather than field names or unintended positional fallback. | | 43 | Scales product locks | Product locks were not applied across every remote mutation path. Apply them consistently to value writes, filling calculations and related recipe/controller operations while retaining permitted updates. | | 44 | Event permission caching | Cached event decisions could outlive identity, role or node-policy changes. Revalidate the current subscriber, event type and source permissions instead of reusing a stale decision. | | 45 | WoT Modbus destinations | Hostless Modbus forms could imply localhost before loopback checks. Reject them before connecting and validate the effective endpoint. | | 46 | MCP local PKI tools | Peer-trust mutations did not consistently require the caller's `CertificateAdministration` grant. Check the capability before trust and untrust operations. | | 47 | Robot-control HTTP bridge | The bridge lacked consistent Host, Origin and port validation and caller-specific session grants. Apply those checks before borrowing the OPC UA session and retain supported local clients. | | 48 | Subscription transfers | Transferred subscriptions could retain cached notifications after the destination's permissions changed. Require authorization continuity between source and destination identities and roles, invalidate stale retransmission data when continuity fails, and preserve retransmission behavior for compatible transfers. | ### Compatibility notes - Replication uses the versioned NSRP v1 policy envelope. Upgrade peers together and re-encode or reseed legacy topology from the authoritative model; missing permission metadata is not inferred from old records. - Integrity-only DTLS peers must be upgraded together for the full sequence/epoch MAC. AEAD behavior is unchanged. - Distinct PubSub key groups require separate connections. - Custom sessions that cannot capture the actual encrypted transport, and subscription providers that cannot honor a requested range, are rejected rather than silently changing the requested behavior. - Subscription transfers retain retransmission data only when the destination authorization remains compatible with the source snapshot; otherwise the old sequence numbers and cached data are invalidated. ## Related Issues No separate tracking issue is linked. The individual corrections are listed above. ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [ ] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received.
42 KiB
Geometric Measurement Systems
The Opc.Ua.GMS* libraries implement
OPC 40210 Geometric Measurement Systems.
They combine the released GMS model with the existing
MachineTool runtime, Machinery building blocks
and result services. A system remains a native GMSType instance, not a
base MachineTool object with an unrelated result tree.
The reference document is 1.0.1; its released normative NodeSet is 1.0.0. GMS version 1 is a monitoring and result model. It does not define new measurement-start, physical-calibration, GMS job-start or correction-actuation commands.
Contents
- Library layout
- Models and dependencies
- Quick start - server
- Quick start - client
- Server hosting and composition
- System builder and provider contracts
- Results, characteristics and corrections
- Typed client workflows
- Lifetime, cancellation and bounds
- MCP integration
- Conformance and support limits
- Model provenance
- Samples and related documentation
Library layout
The names in this table are assembly/project names. NuGet package IDs add
the OPCFoundation.NetStandard. prefix, for example
OPCFoundation.NetStandard.Opc.Ua.GMS.Server.
| Library | Responsibility |
|---|---|
Opc.Ua.GMS |
Released generated states, identifiers, loaders, typed proxies and shared observation/result contracts |
Opc.Ua.GMS.Server |
Native GMS builder, equipment/production binding, result publisher, bounded reference stores and hosting |
Opc.Ua.GMS.Client |
Discovery, bounded native reads, results/downloads and observations over an existing session |
Opc.Ua.Mcp.GMS |
Six bounded GMS MCP workflows over the host's named sessions |
Model/server/client target .NET Framework 4.8 and .NET 8, 9 and 10.
MCP targets .NET 8, 9 and 10. GMS packages, including .Debug variants, follow
the normal root version policy; they are not independently forced to preview.
The model is server/client-independent, and the typed client does not depend
on the server runtime.
Models and dependencies
Reference edition and wire namespace
| Item | Value |
|---|---|
| Reference document | OPC 40210, edition 1.0.1 |
| Released NodeSet model version | 1.0.0, published 2023-07-01 |
| Wire namespace | http://opcfoundation.org/UA/GMS/ |
| Generated CLR namespace | Opc.Ua.GMS |
| Shared contract namespace | Opc.Ua.GMS.Contracts |
| Model metadata | GmsModel.ReferenceEdition, Version, NamespaceUri, SourceCommit |
GmsModel.GetNamespaceUris() returns the dependency closure in order.
AddGmsTypeSystem loads/registers GMS and its existing dependencies rather
than generating duplicate copies of their types.
Generated proxies resolve namespace-qualified identities; clients must not
assume the GMS namespace has a fixed numeric index.
Reuse of MachineTool and Machinery
GMS derives from MachineTool and consumes DI, IA, Machinery, MachineTool's ISA-95/Machinery Jobs dependency closure, and Machinery Result. Although the MachineTool assembly also contains WMTP, GMS does not select WMTP, PA-DIM or ProcessValues merely by referencing that assembly.
The root, identification, tools, jobs/parts and result management keep their GMS-derived types. Shared machinery supplies registration, model-change tracking, state observations, conditions, metadata handles and temporary-file transfer. The server reuses these services rather than implementing another subscription, result-transfer or session-lifetime protocol.
The NodeSet's required-model versions are publication dependencies, not a second set of generated assemblies to install beside the repository's current compatible models. In particular, retained MachineTool Production remains available for GMS despite its partial deprecation in newer MachineTool editions.
Quick start - server
Build a mandatory-only system
Every complete system needs an explicit equipment provider, result store and nameplate. The following configuration helper receives those application-owned services and gives the resulting runtime to an acquisition coordinator:
using System;
using Microsoft.Extensions.DependencyInjection;
using Opc.Ua;
using Opc.Ua.GMS.Contracts;
using Opc.Ua.GMS.Server;
using Opc.Ua.Server.Hosting;
static IOpcUaServerBuilder AddInspectionServer(
IServiceCollection services,
IGmsProvider observations,
IGmsResultStore results,
Action<GmsHandle> retainRuntime)
{
return services.AddOpcUa()
.AddServer(options =>
{
options.ApplicationName = "GmsServer";
options.ApplicationUri = "urn:example:gms-server";
options.EndpointUrls.Add("opc.tcp://localhost:62559/GmsServer");
})
.AddGms()
.ConfigureGms(async (context, ct) =>
{
GmsHandle system = await context.AddGms("InspectionCell")
.WithProvider(observations)
.WithIdentification(identity =>
{
identity.Manufacturer = new LocalizedText("Example manufacturer");
identity.SerialNumber = "CMM-001";
identity.ProductInstanceUri = "urn:example:inspection:CMM-001";
})
.WithResults(results)
.BuildAsync(ct);
retainRuntime(system);
});
}
Invoke it during host construction, then await the normal application/server startup and shutdown. Configure certificates, trust, endpoints and user authentication through server hosting and CertificateManager.
For a simulation, InMemoryGmsProvider(new GmsSystemSnapshot()) starts with
explicitly unavailable observations. InMemoryGmsResultStore takes the existing
IServiceMessageContext, such as context.Context.AsMessageContext() inside
configuration. It is a bounded reference store, not durable production storage.
Do not put the returned GmsHandle in an await using scoped to the
configuration callback: that would shut it down as startup completes.
retainRuntime should only retain it for application-managed refresh.
Acquire and refresh observations
IGmsProvider.ReadAsync returns an immutable, coherent GmsSystemSnapshot.
Its MachineTool member carries the inherited revision and observations;
native sensor/calibration/job/part observations extend that same acquisition.
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.GMS.Contracts;
using Opc.Ua.GMS.Server;
static async ValueTask PublishGmsObservationAsync(
InMemoryGmsProvider provider,
GmsHandle system,
GmsSystemSnapshot acquisition,
CancellationToken ct)
{
await provider.SetSnapshotAsync(acquisition, ct);
await system.RefreshAsync(ct);
}
The in-memory adapter assigns a strictly increasing inherited revision. A real provider must do so itself and keep returned graphs immutable. Updating the adapter does not itself refresh OPC UA nodes; the runtime's refresh performs validation and coherent publication. Neither call starts a physical measurement.
Quick start - client
Discover systems and read results
The facade borrows an existing connected session and discovers actual GMS subtypes, not arbitrary objects in a namespace:
using System;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.GMS.Client;
using Opc.Ua.GMS.Contracts;
using Opc.Ua.Machinery.Result;
static async ValueTask ReadInspectionResultsAsync(
ISession session, ITelemetryContext telemetry, CancellationToken ct)
{
await using GmsClient client = session.CreateGmsClient(telemetry);
ArrayOf<GmsDeviceEntry> systems = await client.DiscoverAsync(ct);
for (int index = 0; index < systems.Count; index++)
{
GmsDeviceEntry system = systems[index];
GmsSystemReadSnapshot snapshot = await client.ReadSnapshotAsync(system.NodeId, ct);
GmsResultClient results = client.GetResults(system.NodeId);
ArrayOf<string> ids = await results.QueryAsync(16, cancellationToken: ct);
Console.WriteLine($"{system.NodeId}: {ids.Count} result identifiers");
if (ids.Count != 0)
{
ResultDataType result = await results.GetByIdAsync(ids[0], ct);
Console.WriteLine(result.ResultMetaData!.ResultId);
}
}
}
QueryAsync returns up to the requested result count; it is not a statement
that the entire store contains no more results. Every acquired metadata handle
is released by the typed operation. Use ReadPublishedResultsAsync when raw
result-variable DataValue quality and acquisition timestamps are needed,
rather than treating the returned ResultDataType envelope as a DataValue.
Factory and session ownership
AddGmsClient() extends an existing AddOpcUa().AddClient(...) registration
and registers IGmsClientFactory. CreateAsync uses the configured
container-cached ManagedSession and borrows it.
Direct constructors, session.CreateGmsClient(...), GmsClientFactory and
GmsResultClientFactory do not transfer session ownership. Disposing one GMS
facade drains only its own work and leaves other consumers connected.
Cancellation after session acquisition or a failed facade constructor also
leaves the shared session with its owner.
The DI container closes its cached session at shutdown. An application that creates an independent session must close/dispose it separately. See client hosting and the MachineTool lifetime rules.
Server hosting and composition
Standalone and existing Machinery owners
AddGms registers one Machinery owner with
MachineryParts.BuildingBlocks | MachineryParts.Result.
GmsServerOptions.InstanceNamespaceUri chooses the standalone application
namespace. Optional system features are still off until the per-instance
builder selects them.
When the owner already exists, use AddGmsToMachinery, not a second AddGms:
using Microsoft.Extensions.DependencyInjection;
using Opc.Ua.Machinery.Server;
using Opc.Ua.Server.Hosting;
static IOpcUaServerBuilder AddGmsModelsToCell(IOpcUaServerBuilder server)
{
return server.AddMachinery(options =>
{
options.Parts = MachineryParts.BuildingBlocks | MachineryParts.Result;
options.InstanceNamespaceUri = "urn:example:cell:instances";
}).AddGmsToMachinery();
}
The example establishes the owner first; omit AddMachinery if the application
already registered it. The existing owner must include both required parts.
ConfigureGmsFor<MachineryNodeManager>(...) uses that owner's context.
Multiple systems require independent per-instance providers and stores.
MachineTool, WMTP, cranes and Machine Vision can share the owner when all their required model/part selections are present. See the CompanionSuite sample for the combined setup.
Custom owners and one native root
ConfigureGmsFor<TNodeManager> targets a compatible existing DiNodeManager
subtype with the shared Machinery construction services.
GmsModelProvider plugs into its existing model-provider pipeline.
The model loader does not create another DeviceSet or MachineTool owner.
The native builder uses AddMachineTool<GMSState> with GMS-owned factories.
WithMachinery(...) adds bindings to the same unpublished typed root; that
callback must not call BuildAsync itself. Registration, rollback and
root-bound resources follow the existing owner's transaction.
For advanced result-only binding on an already staged GMS root,
WithGmsResults(store, out binding, options) adopts its declared
GMSResultManagementState. binding.CreatePublisher(machineHandle) is tied
to that exact registered root. It does not create another base AddIn and cannot
be attached to another machine. The staged root still needs its non-result
GMS requirements; this binding alone is not a full GMS system.
System builder and provider contracts
Mandatory defaults and optional features
GmsFeatures.None retains mandatory identification, equipment, monitoring,
production and result-management structure. It does not silently install
optional sensors, files or command adapters.
GmsBuilder.WithOptions(GmsSystemOptions) controls native optional surfaces:
| Feature | Required data or relationship |
|---|---|
Identification |
Workspace and device-subclass observations |
Sensors |
Native sensor/multisensor inventory, including qualification countdowns |
DynamicSensors |
Requires Sensors; permits inventory changes through shared lifetime handling |
Loading |
Part-loading state and optional loading position |
ToolMonitoring |
Requires Sensors; references actual active sensor keys |
Accessories |
Rotary tables, sensor exchange racks and tip exchange racks |
AdditionalSensors |
Environmental/additional analog measurements with class and units |
Calibration |
Calibration state, dates, interval/certificate and forecast observations |
Production |
GMS jobs/parts on retained MachineTool Production |
DynamicProduction |
Requires Production; permits changing production plans |
SensorWarnings |
Requires AdditionalSensors, not primary measurement sensors |
Selected data must be complete and type-correct before publication.
Invalid feature combinations fail with BadConfigurationError.
Enabling All does not supply the missing equipment observations.
WithMachineToolFeatures selects inherited monitoring independently.
WithProduction, WithPrognoses and WithNotifications reuse the corresponding
MachineTool options, while the GMS builder retains its own native job, part and
sensor factories.
Sensors, multisensors and qualification
GmsSystemSnapshot.Sensors describes native single sensors and nested
multisensors. GmsSensor.Tool supplies the inherited tool identity/life/location
data; native fields retain their declared types, including class, capabilities,
alignment, axes, probe information, measuring/working range and resolution.
GmsQualifiedLife.RemainingHours is a Double countdown in hours under the
native Qualified life entry. The binding preserves Qualified/Time and
IsCountingUp=false. Negative remaining hours after expiry are meaningful
and are not clamped to zero. Acquisition quality, geometric qualification
status and countdown expiry are different facts.
Dynamic sensor changes use stable keys and the existing MachineTool tool
module. ToolMonitoring.ActiveSensorKeys resolves the accepted sensor
inventory, not arbitrary identifiers from another instance. The runtime
rejects incompatible references before committing the acquisition.
Workspace, loading and accessories
Workspace and device-subclass values belong to GmsIdentificationObservation.
Part loading is a GmsLoadingObservation; active-tool monitoring is a
GmsToolMonitoringObservation. Accessory kinds use the real released rotary
table and exchange-rack types, with their own identification where required.
These are observations, not automatic part loading, tool changing or calibration procedures. Static optional-node shapes are configured before publication. A provider cannot add a new optional property to a fixed published slot merely by changing an untyped payload.
Additional sensors and warnings
GmsAdditionalSensor preserves its identifier, class, engineering units and
actual DataValue. These sensors measure environmental or auxiliary conditions
not directly involved in the geometrical measurement itself.
GmsSensorWarning binds a retained SensorWarningAlarmType condition to an
additional sensor using SensorKey, or an explicit compatible InputNodeId.
Its limits, active state, severity, message and acquisition metadata remain
provider observations. The warning's input is not a primary probe's
qualification countdown.
ErrorCode is also a provider-owned observation: its runtime instance is
read-only before registration, regardless of AllowedOperations. Ordinary
Value writes return BadNotWritable; providers update it through accepted
snapshots and RefreshAsync, without granting a second cache-write path.
Use the separate inherited MachineTool notification provider for unrelated operator alerts. Standard condition methods remain subject to their allowed operations and the server's authorization. Reading or observing a warning does not acknowledge or confirm it.
Calibration observations
GmsCalibrationObservation includes the acquired calibrated state, date of
calibration, forecast time and optional interval, preparation time and certificate
reference. GmsHandle.CalibrationHistory is a bounded application-side history
of accepted observations, not a new historical-access or calibration-execution
service.
The client preserves these values and returns null when the optional calibration surface is absent. It does not fetch a certificate URI automatically, derive a calibration result from elapsed host time or command the measuring equipment.
Native production and inherited behavior
GMS jobs and parts extend the retained MachineTool Production model.
The provider supplies matching keys across inherited job/program/part-set/part
observations and native GmsJobObservation/GmsPartObservation.
Native fields include batch identity, duration/remaining time, measurement
reason, part descriptions and catalog-backed production metadata.
Catalog observations preserve their declared Number scalar/array representation,
enumeration metadata and ValueAsText; they are not normalized through an
untyped string or floating-point representation. Rejected acquisition does
not commit new job/part indexes.
Inherited production statistics remain implemented. This is distinct from claiming the complete GMS Production facet, whose undefined requirement is documented below. No GMS-specific job execution verbs are introduced.
Results, characteristics and corrections
Per-instance stores and result binding
IGmsResultStore belongs to the model contract assembly and is injectable.
It supplies newest/by-ID reads, a bounded newest-first ID list, atomic writes
and per-ID acknowledgement results. It must honor cancellation and return
immutable snapshots. The same store object cannot back two active GMS bindings.
InMemoryGmsResultStore rejects count/byte overflow instead of silently evicting
results. Applications choose retention explicitly with RemoveAsync.
Durability, distributed concurrency and equipment/database transactions belong
to an application implementation of IGmsResultStore.
GmsResultBindingOptions controls the result subtree separately from system
features:
| Option | Meaning |
|---|---|
PublishedResults |
Fixed ring of native result-variable slots; default 4, zero selects method-only access |
IncludeUsedTools |
Exposes GMSResultType.UsedTools |
EnableIntermediateEvents |
Allows concrete intermediate-result events |
EnableFileTransfer |
Selects the shared temporary-file download path |
Corrections |
Null omits the optional folder; an empty array exposes an explicitly empty folder |
Limits |
Result, metadata, file, stored-byte, characteristic and correction budgets |
Selected UsedTools properties are read-only provider provenance on every
published result slot, including before its first result is acquired. Ordinary
Value writes return BadNotWritable; GmsResultPublisher.PublishAsync remains
the update path and retains acquisition quality and timestamps. Unselected
properties remain absent, and zero published slots still supports method-only
result access.
Default distinction: GmsBuilder.WithResults(store) disables file transfer
for the mandatory-only system. A separately constructed
new GmsResultBindingOptions() defaults EnableFileTransfer to true.
Set that property explicitly when supplying an options object.
Publish an acquired result
GmsResult wraps an acquired DataValue carrying the unchanged Machinery
ResultDataType. The envelope must contain ResultMetaData with a nonempty
ResultId; optional structured fields need their encoding-mask bits.
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.GMS.Contracts;
using Opc.Ua.GMS.Server;
using Opc.Ua.Machinery.Result;
static ValueTask PublishInspectionAsync(
GmsHandle system,
ResultDataType equipmentResult,
StatusCode acquisitionQuality,
DateTimeUtc acquiredAt,
CancellationToken ct)
{
var sample = new DataValue(
Variant.FromStructure(equipmentResult), acquisitionQuality, acquiredAt, acquiredAt);
return system.Results.PublishAsync(new GmsResult(sample), ct);
}
The provider supplies the real envelope, quality and acquisition time; the server does not invent a successful measurement. The result boundary copies structured metadata, samples and buffers. Stable result-variable slots retain their projection and acquisition metadata when the ring advances, even if the backing store later removes an older entry.
Final results emit a concrete subtype of Machinery's abstract
ResultReadyEventType. The concrete type is registered in the application
namespace while retaining the standard base relationship.
Intermediate characteristics and tolerances
An intermediate result requires at least one GmsCharacteristic. Its geometric
validity and tolerance evaluation are independent of OPC UA quality:
using Opc.Ua;
using Opc.Ua.GMS;
using Opc.Ua.GMS.Contracts;
using Opc.Ua.Machinery.Result;
static GmsResult CreateIntermediateInspection(
in DataValue resultEnvelope,
GmsAnalogValue measurement,
GmsTolerance lower,
GmsTolerance upper,
ResultEvaluationEnum evaluation,
bool geometricallyValid)
{
return new GmsResult(resultEnvelope,
characteristics:
[
new GmsCharacteristic("Bore")
{
Identifier = "BoreDiameter",
ResultValue = measurement,
LowerTolerance = lower,
UpperTolerance = upper,
Evaluation = evaluation,
IsValid = geometricallyValid
}
],
programName: "Inspection",
isIntermediate: true);
}
resultEnvelope must contain the same required ResultDataType metadata as a
final result. GmsResult marks intermediate metadata as IsPartial.
Enable intermediate events in the binding before publishing this result.
GmsAnalogValue carries a Double sample and units; a bad/null value represents
unavailable acquisition. Every configured GmsTolerance carries its mandatory
ToleranceLimitEnum form. Nominal values, importance class and formula are
separate optional fields. Neither tolerance evaluation nor IsValid is inferred
from a Good/Bad UA status.
Intermediate events derive from IntermediateResultEventType, not the
final-result event type. Generated decoding preserves the explicitly selected
characteristic placeholders and their qualified names.
Calculated corrections are observations
Each GmsCorrection requires an identifier, a characteristic identifier and at
least one relative or absolute GmsAnalogValue. Configure the correction
objects through GmsResultBindingOptions.Corrections, then update their
observations using system.Results.PublishCorrectionAsync(correction, ct).
Correction values, limits, identifiers and characteristic references are read-only to remote clients on the published instance. Updates use the server-side publisher; ordinary Value writes cannot falsify an observation or bypass its fixed-reference checks.
The configured identity, characteristic reference and optional-node shape remain fixed. Changing them requires rebuilding the instance, not silently retargeting a correction node. Publishing or reading a correction never applies it to a machine.
Queries, metadata handles and files
GMS binds the real GMSResultManagementState through the existing
Machinery Result implementation:
GetLatestResult,GetResultByIdandGetResultIdListFilteredreturn session-bound metadata handles that pin their result.ReleaseResultHandlereleases that caller's handle. Session termination reclaims its handles without invalidating another session's results.- Queries use the standard
ContentFilterand orderedRelativePathlist, with the Machinery evaluator's NULL and ordering rules. - Result files use
GenerateFileForReadand the existingTemporaryFileTransferClient/UaFileStreamworkflow, not a private protocol. - Result acknowledgement returns domain statuses; it does not delete results or start a measurement.
The shared result-transfer path is read-only. Its required write-side methods return the standard refusal statuses instead of pretending to accept uploads. Metadata/file cleanup, cancellation and retained-root deletion use the shared Machinery result lifetime.
Typed client workflows
System reads and optional absence
GmsClient API |
Workflow |
|---|---|
DiscoverAsync |
Native GMSType and vendor-subtype discovery |
ReadSnapshotAsync |
Identification and inherited/native system values |
ReadEquipmentAsync |
Sensors/multisensors, qualification, loading, tool monitoring and accessories/additional sensors |
ReadCalibrationAsync |
Optional calibration observation; absent surface is null |
ReadProductionAsync |
Native job/part metadata and retained production |
GetProxy |
Generated GMSTypeClient |
GetResults |
Bounded GmsResultClient on the same session |
Reads preserve NodeIds, optional presence, numeric types, quality and acquisition
timestamps. A null ArrayOf<T> is checked with IsNull, not a CLR nullable
wrapper. An absent optional DataValue is distinct from a present bad-quality
sample. Missing mandatory structure and service/access errors are not converted
to successful empty snapshots.
Separate client service calls are not an atomic equipment-history transaction. Use the returned acquisition metadata and application correlation when a cross-read snapshot is required.
Result access and downloads
GmsResultClient API |
Ownership or outcome |
|---|---|
GetLatestAsync, GetByIdAsync |
Detached result envelope; call-owned metadata handle released |
QueryAsync |
Bounded standard filter/order query; query handle released |
ReadPublishedResultsAsync |
Original result-slot DataValues and optional UsedTools |
ReadCorrectionsAsync |
Native correction values/units, not commands |
DownloadAsync |
Bounded ByteString or streamed download into a caller-owned destination |
AcknowledgeAsync |
One unchanged domain result per identifier; no implicit retry |
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.GMS.Client;
static ValueTask DownloadInspectionAsync(
GmsResultClient results,
string resultId,
Stream destination,
CancellationToken ct)
{
return results.DownloadAsync(resultId, destination, ct);
}
The destination stays open; after failure it may contain a partial prefix.
The client releases its remote file and metadata resources on success, failure
and cancellation. The ByteString overload shares that implementation.
GmsResultDomainException represents a nonzero result-method domain error,
separately from ServiceResultException and cancellation. When both an operation
and its cleanup fail, both errors remain visible.
Bounded observations and generated proxies
ObserveDataAsync, ObserveEventsAsync and ObserveConditionsAsync reuse the
MachineTool collector and MachineToolObservationOptions. Windows include
setup, are at most 60 seconds, and retain at most 100 notifications within a
finite decoded-byte budget. Condition refresh is explicit; observation does
not acknowledge/confirm warnings or execute jobs.
GmsResultClient.ObserveIntermediateResultsAsync composes an existing
IStreamingSubscription. Select the real, instance-qualified characteristic
placeholders through CreateIntermediateResultFilter; do not assume their
namespace is the GMS model namespace. The readiness-callback overload signals
only after actual monitored-item creation succeeds.
The enumerator removes its own monitored item, while the caller retains the
streaming subscription.
GetProxy and GmsResultClient.Root expose generated proxies for advanced
navigation. Reuse them rather than manually composing unqualified browse paths
or low-level Call arrays. Event fields retain their actual values but do not
gain invented per-field acquisition timestamps; use result variables for raw
sample quality/time.
Lifetime, cancellation and bounds
Coherent refresh and retained publication
The GMS binding prepares native projections within MachineTool's
acquire/validate/commit/publish transaction. Rejected observations do not replace
accepted sensor, job or part indexes. Observation and CalibrationHistory
reflect accepted state.
If notification publication fails after commit,
RetryPublicationAsync retries retained publication without reacquiring or
repeating equipment effects. It is not an automatic retry of a remote
acknowledgement or a physical operation.
Dynamic sensor/production removal observes the final-value publication barrier: the exact required terminal entry must enter a real Publish response before its subtree can be removed. Queuing, equality with an older sample, network delivery and client acknowledgement are different boundaries.
Resource ownership and cleanup
| Resource | Owner |
|---|---|
IGmsProvider, IGmsResultStore, inherited notification/provider services |
Application; use per-instance selections |
| GMS root and result binding | One MachineTool/Machinery registration |
| Runtime-created observation and result-transfer resources | The corresponding runtime or call |
| Session used by any GMS facade/factory | Application or DI container |
| Stream passed as a download destination | Caller |
| Streaming subscription passed to intermediate observation | Caller; enumerator owns only its item |
GmsHandle.DisposeAsync drains the native binding and shared runtime.
Deleting the bound root or stopping its manager also releases the registration's
resources. Providers must honor cancellation and avoid reentering their own
refresh/disposal path.
GmsClient.DisposeAsync cancels and joins its admitted operations and inherited
observations without closing a borrowed session. Cancellation is not proof that
a submitted acknowledgement was undone; inspect the result and never retry an
uncertain mutation implicitly.
Configuration limits
| Options | Representative defaults |
|---|---|
GmsSystemOptions |
128 sensors, 128 accessory/additional-sensor slots, 32 calibration-history entries, 128 warning identities, 1,024 array entries |
GmsClientOptions |
128 native elements, 16,384 examined references, 1 MiB raw sample payload |
GmsResultLimits |
64 results, 256 KiB metadata, 16 MiB per file, 64 MiB total stored bytes, 64 characteristics/used tools/corrections |
Bounds are validated before service work or instance publication. Provider-side, client-side and transport quotas are independent; the most restrictive applicable bound wins. Oversize complete payloads fail rather than silently omit measurements. A result query's explicit maximum is a query selection limit, not unlimited export.
MCP integration
Select or embed the GMS profile
opcua-mcp --profile gms
For embedding, select only the GMS catalog and shared connection tools:
using Microsoft.Extensions.DependencyInjection;
using Opc.Ua.Mcp;
static IMcpServerBuilder AddGmsMcp(IServiceCollection services)
{
services.AddOpcUaMcpCore().AddOpcUaMcpGms();
return services.AddMcpServer()
.WithStdioServerTransport()
.WithOpcUaMcpFilters()
.WithOpcUaGmsTools(McpToolProfile.Gms);
}
The profile-set overload composes catalogs and deduplicates common connection
tools. It borrows host-managed named sessions. Omit sessionName only when
exactly one session is connected.
Catalog and explicit acknowledgement
| Tool | Workflow |
|---|---|
gms_list_systems |
Actual GMS/vendor-subtype inventory |
gms_read_system |
System, Equipment, Calibration, Production or Capabilities |
gms_read_results |
Latest, ById, bounded Query, raw Published slots or Corrections |
gms_download_result |
Bounded inline result bytes; no host-local path |
gms_observe |
Data, Events, explicit retained Conditions, or IntermediateResults |
gms_acknowledge_results |
Standard result acknowledgement with confirmAcknowledgement=true |
Passive workflows do not acknowledge results/conditions, fetch calibration certificate URLs, execute measurements/jobs or apply corrections. Acknowledgement is the separate standard state change, with explicit consent and authoritative server permission; per-result domain failures remain visible.
For gms_read_results, native query options belong inside the query object:
{
"systemNodeId": "ns=12;s=InspectionCell",
"operation": "Query",
"query": {"filterField": "JobId", "filterValue": "J-1", "orderBy": "ResultId"},
"maximumResults": 20,
"sessionName": "metrology"
}
Use the NodeId actually returned by discovery; the namespace index above is illustrative. This remains a standard Machinery filter, not a new GMS query protocol.
Host policy and bounded replies
Catalog selection is not an authorization grant. Privileged/live host capabilities are independently default-off and require explicit caller grants/network policy when enabled. A confirmation argument does not create OPC UA authority. See MCP execution policy.
The GMS tools cap inventory/results/notifications at 100, observation windows at 60 seconds, downloads at 524,288 bytes and complete UTF-8 replies at 1 MiB (default 128 KiB). Native read limits may be tighter. The MCP query workflow requests one extra identifier to detect a result set that exceeds the requested complete-reply bound.
Unknown/duplicate query properties and incorrectly typed selector values fail
before session use. Incomplete or lossy retained-condition refresh is an MCP
error, not an apparently complete empty set.
Calls release their own subscription, metadata and file resources; cancellation
must reach the server through the transport, such as the standard MCP
notifications/cancelled notification. No persistent handle or implicit retry
is introduced.
See the GMS MCP reference for exact schemas, characteristic selection, bounds and cancellation behavior.
Conformance and support limits
Supported surfaces
| Feature | Support |
|---|---|
| OPC 40210 reference 1.0.1 / released model 1.0.0 | Implemented |
| Sensors/multisensors, qualification, workspace, loading, tools and accessories | Explicit provider-backed observations |
| Calibration and additional environmental sensors | Monitoring; no physical calibration |
| Native GMS jobs/parts and inherited production statistics | Implemented |
| Results, characteristics/tolerances, corrections, queries, downloads and events | Native types with shared result services; corrections do not actuate |
SensorWarningAlarmType |
Additional environmental-sensor conditions |
| Standalone/shared hosting, typed clients, bounded MCP and NativeAOT | Supported through the same runtime APIs |
| Complete GMS Production facet | Withheld because of the undefined requirement below |
| Invented measurement-start, calibration-execution or GMS job/correction-control verbs | Not supplied |
| External OPC Foundation certification of a deployment | Not claimed |
The undefined Production statistics requirement
OPC 40210 section 12.3.2.7
requires GMS_ProductionStatisticsType for the Production conformance unit.
The released specification does not define that type or its NodeId, and
Annex A.1's normative NodeSet
contains no such definition.
The implementation does not fabricate a type, reuse a different type under that name or silently claim the complete Production facet. Defined native GMS jobs/parts and inherited MachineTool statistics remain supported. This is a specific upstream conformance exception, not a statement that all production monitoring is absent.
Instance capabilities versus certification
GmsHandle.Capabilities describes current native conformance units,
dependency-qualified server profiles and the precise upstream exception.
Contributions follow successfully committed live instances and selected
features, not assembly references or namespace presence.
The profile definitions are in OPC 40210 section 12. Endpoint security, certificates, user roles, quotas and actual equipment behavior remain deployment responsibilities. Profiles records repository-wide scope; it is not an external certification claim.
The GMS tests cover native model/proxy identity, equipment and production, result filtering/transfers, quality preservation, warning sources, rejected acquisitions and real-session ownership/cleanup. The test fixtures and runtime capability checks are distinct from certification of a vendor device.
Model provenance
The normative model is pinned to UA-Nodeset revision
96a08945425d0295edb3607d2a38f6841ef44046.
| Source | Role |
|---|---|
Model/opc.ua.gms.nodeset2.xml |
Original lowercase publication filename and normative wire identities |
Opc.Ua.GMS.Derived.NodeIds.csv |
Locally derived symbolic drift guard for the generator |
GmsModel |
Reference/model versions, namespace closure, source commit and LF-normalized NodeSet SHA-256 |
Opc.Ua.GMS.csproj |
Explicit generator namespace, model URI and existing dependency assemblies |
The NodeSet supplies numeric identifiers; a derived identifier table is not a replacement specification. Shared MachineTool, Machinery and DI definitions are consumed through their existing assembly payloads. See ModelDependencies and Machinery's identifier-table explanation.
Samples and related documentation
- GMS sample pair: mandatory/advanced simulations, independent providers/stores, encrypted loopback sessions, bounded observations, result download/acknowledgement and cancellation cleanup.
- CompanionSuite: GMS alongside MachineTool, WMTP, cranes and Machine Vision on one owner.
- MachineTool and wireless peripherals, Machinery, Device Integration and Machine Vision.
- Final-value publication, model-change tracking, alarms and conditions, file services and subscriptions.
- MCP server and GMS MCP package.
- Developer guide for builds, framework selection and tests.