# Description _Describe the changes here to communicate to the maintainers why they should accept this pull request. By default - this will become the Commit message after merging and thus define history._ Add provider-backed MachineTool, bundled WMTP, GMS and CranesHoists companions using the existing Machinery/MachineVision fluent, DI and typed-client approach. **The PR is open/non-draft by user choice. Current head `ecc4a7d6ca3e8004cf28ab54da605da302d026bf` is mergeable and has 108 successful checks, six intentionally skipped checks, zero failures and zero pending checks. All five primary workflows completed successfully at that exact head.** | Family | Released specification/model | Packaging | | --- | --- | --- | | MachineTool | OPC 40501-1, 1.02.0 | Model, Server, Client and MCP; normal root versions | | WMTP | OPC 40505, 1.0.0 | Bundled in MachineTool; distinct CLR/wire namespace and independent WMTP-only hosting | | GMS | OPC 40210 reference 1.0.1, normative model 1.0.0 | Model, Server, Client and MCP; normal root versions | | CranesHoists | OPC 40020-1, 1.00 | Model, Server, Client and MCP; all four plus Debug variants forced preview | There are **12 new packable projects**. No separate WMTP package/profile/guide, root version bump, tag, release or package publication is included. Mandatory instance structure is the default; selected optional features require explicit providers. The implementation reuses Machinery results/jobs/files/ProcessValues and Robotics topology, including retained MachineTool Production, defined GMS jobs/parts, modern ISA95/Machinery jobs, shared-owner hosting and isolated instances alongside MachineVision. Runtime and integration fixes preserve raw quality/timestamps, coherent acquisition, namespace ownership, accepted-configuration state, borrowed-resource lifetime, session/node/file cleanup, terminal-value publication, event propagation and endpoint-aware capability publication. Firmware/licence transfer means provider acceptance, not installation/activation. Cancellation does not silently abort another caller's operation. The later upstream MCP expansion is integrated without losing either family set or its default-off privileged/live execution policy. ### Conformance and scope Canonical model inputs are pinned to UA-Nodeset revision `96a08945425d0295edb3607d2a38f6841ef44046`. **GMS upstream exception:** [Annex A.1](https://reference.opcfoundation.org/specs/OPC-40210/annex-a) makes the released NodeSet normative, but §12.3.2.7 requires undefined `GMS_ProductionStatisticsType`. Inherited statistics and defined jobs/parts are implemented without inventing a standard type/NodeId; the complete GMS Production facet is withheld. The requirement ledger records **102 positively evidenced CU rows, 96 directly negative-tested rows and six explicit negative-evidence limitations**. All 648 test-class/name/TRX links were independently resolved to passing cases. The six limitations concern Stacklight, retained legacy PowerOnDuration, Machinery PowerOnDuration, WorkingUnit, Obligation and MaintenanceMode. They are not claims that every invalid-input or hardware-control behavior was tested. The undefined GMS requirement is outside the 102 defined CUs. No external certification claim is made. Retained baseline complete-assembly line coverage, with generated code retained and no source exclusions: | Family | Model | Server | Client | | --- | ---: | ---: | ---: | | MachineTool/WMTP | 83.80% | 91.98% | 84.74% | | GMS | 95.51% | 90.63% | 88.38% | | CranesHoists | 92.76% | 94.45% | 92.24% | These nine measurements predate the later focused review fixes and merges; they are not relabelled as new `ecc4a7d6c` percentages. The current head's separate CI code-coverage check succeeded. No new exclusions or threshold relaxations were introduced. ## Related Issues - Fixes [#4764](https://github.com/OPCFoundation/UA-.NETStandard/issues/4764), the implementation/design and acceptance tracker. ## 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. - [x] I have added all necessary documentation. - [ ] 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. - [x] I have addressed **all** PR feedback received.
41 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.
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 |
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).
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.