Files
UA-.NETStandard/docs/MachineTool.md
Marc Schier 0374e19121 Add MachineTool, WMTP, GMS and CranesHoists companions (#4765)
# 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.
2026-10-15 15:15:46 +02:00

45 KiB

Machine Tools and Wireless Peripherals

The Opc.Ua.MachineTool* libraries implement released OPC 40501-1 MachineTool and OPC 40505 Wireless Machine Tool Peripherals (WMTP). They follow the same model, fluent server-builder and typed-client approach as Machinery and Machine Vision.

WMTP is bundled in the MachineTool model, server, client and MCP packages and in this guide. It retains its own wire and CLR namespaces. A wireless-only server does not need a MachineToolType instance.

These libraries expose equipment observations and explicitly bound standard requests. They are not CNC executors, radio/pairing drivers, firmware installers or equipment safety systems.

Contents

Library layout

The names below are assembly/project names. The corresponding NuGet package ID is OPCFoundation.NetStandard. followed by that name; for example, OPCFoundation.NetStandard.Opc.Ua.MachineTool.Client.

Library Responsibility
Opc.Ua.MachineTool Both generated models, node states, identifiers, loaders, client proxies and shared provider/snapshot contracts
Opc.Ua.MachineTool.Server Fluent machine/peripheral builders, acquisition and publication, provider-backed methods and hosting extensions
Opc.Ua.MachineTool.Client Subtype-aware discovery, bounded reads/observations, generated service navigation and WMTP workflows; no server-runtime dependency
Opc.Ua.Mcp.MachineTool The combined MachineTool/WMTP MCP catalog over host-owned named sessions

The first three libraries target .NET Framework 4.8 and .NET 8, 9 and 10. The MCP library targets .NET 8, 9 and 10. They follow the repository's normal root package-version policy, including .Debug variants; they are not independently forced to preview. The released specification version is distinct from the NuGet version. NativeAOT application examples use the same APIs.

Models and dependencies

Versions and namespaces

Model Version Publication date Wire namespace CLR namespace
MachineTool, OPC 40501-1 1.02.0 2024-11-01 http://opcfoundation.org/UA/MachineTool/ Opc.Ua.MachineTool
Wireless Machine Tool Peripherals, OPC 40505 1.0.0 2025-11-01 http://opcfoundation.org/UA/WMTP/ Opc.Ua.MachineTool.WMTP

Use namespace URIs, generated identifiers and proxies rather than hard-coded namespace indexes. MachineToolModels.GetNamespaceUris(parts) gives the selected non-core closure in dependency order. AddMachineToolTypeSystem loads that closure into an existing node collection or registers its encodeable factories; the caller commits a factory builder. Model loading alone does not instantiate a machine or grant any capability.

Package dependencies versus loaded models

The single model package references the union of both dependency graphs, but runtime model selection remains independent:

MachineToolParts Runtime closure beyond UA core
MachineTool (default) DI, IA, Machinery building blocks, ISA-95 Job Control V2, Machinery Jobs and MachineTool
Wmtp DI, IA, Machinery building blocks, IRDI, PA-DIM, Machinery ProcessValues and WMTP
All Both closures, deduplicated

The owner needs Machinery BuildingBlocks, plus Jobs for MachineTool or ProcessValues for WMTP. Standalone registration selects those parts. Referencing the WMTP-containing assembly does not cause a MachineTool-only endpoint to expose WMTP, nor does a WMTP-only endpoint require ISA-95 jobs.

The retained MachineTool Production declarations are still present in 1.02.0, even where deprecated in favor of Machinery Jobs. They are implemented rather than removed: older clients and GMS still depend on their wire identity.

Quick start - server

Register a mandatory-only machine

The following host-configuration helper creates one machine on the standard Machinery owner. provider is this machine's application-owned acquisition adapter; retainRuntime stores the returned handle in the application's acquisition coordinator. It should not dispose the handle at callback exit.

using System;
using Microsoft.Extensions.DependencyInjection;
using Opc.Ua;
using Opc.Ua.MachineTool;
using Opc.Ua.MachineTool.Server;
using Opc.Ua.Server.Hosting;

static IOpcUaServerBuilder AddMillServer(
    IServiceCollection services,
    IMachineToolProvider provider,
    Action<MachineToolRuntime<MachineToolState>> retainRuntime)
{
    return services.AddOpcUa()
        .AddServer(options =>
        {
            options.ApplicationName = "MachineToolServer";
            options.ApplicationUri = "urn:example:machinetool-server";
            options.EndpointUrls.Add("opc.tcp://localhost:62558/MachineToolServer");
        })
        .AddMachineTool()
        .ConfigureMachineTool(async (context, ct) =>
        {
            MachineToolRuntime<MachineToolState> machine = await context
                .AddMachineTool("Mill")
                .WithProvider(provider)
                .WithIdentification(identity =>
                {
                    identity.Manufacturer = new LocalizedText("Example manufacturer");
                    identity.SerialNumber = "Mill-001";
                    identity.ProductInstanceUri = "urn:example:mill:001";
                })
                .BuildAsync(ct);
            retainRuntime(machine);
        });
}

Register this while building the application host, then await normal host startup and shutdown. Configure application certificates, trusted peers, endpoint policies and user authentication through the existing server hosting and certificate infrastructure. No trust bypass or embedded credential is needed by the builder.

BuildAsync acquires and validates the provider snapshot before publishing the instance. Missing identification, missing providers, invalid selected capabilities or conflicting identities fail setup; they do not leave an advertised half-machine. The initial default snapshot explicitly marks unavailable acquisitions with bad quality.

Publish an acquired observation

IMachineToolProvider.ReadAsync returns an immutable MachineToolSnapshot. The application decides when to acquire and refresh; a client read is not an instruction to run a program.

For a mandatory-only simulation, create an InMemoryMachineToolProvider(new MachineToolSnapshot()) and later publish an explicitly supplied observation:

using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.MachineTool;
using Opc.Ua.MachineTool.Server;

static async ValueTask PublishMillObservationAsync(
    InMemoryMachineToolProvider provider,
    MachineToolRuntime<MachineToolState> machine,
    MachineOperationMode observedMode,
    DateTimeUtc acquiredAt,
    CancellationToken ct)
{
    await provider.SetSnapshotAsync(new MachineToolSnapshot
    {
        Operation = new MachineToolOperation
        {
            Mode = observedMode,
            Sample = new MachineToolSampleInfo(acquiredAt, StatusCodes.Good)
        }
    }, ct);
    await machine.RefreshAsync(ct);
}

This example supplies only overall operation; the active program remains explicitly unavailable. When optional inventories are enabled, provide their complete coherent observations too. SetSnapshotAsync assigns the next revision in this in-memory adapter. A production adapter must supply monotonically increasing revisions itself and must not mutate a returned graph.

Quick start - client

Discover and read typed instances

The typed facade takes an already connected ISession. It registers the selected model factories, resolves actual subtypes and keeps optional absence separate from a present bad/uncertain DataValue.

using System;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.MachineTool;
using Opc.Ua.MachineTool.Client;

static async ValueTask ReadMachinesAsync(
    ISession session, Opc.Ua.ITelemetryContext telemetry, CancellationToken ct)
{
    await using MachineToolClient client = session.CreateMachineToolClient(telemetry);
    ArrayOf<MachineToolDeviceEntry> devices = await client.DiscoverAsync(cancellationToken: ct);
    for (int index = 0; index < devices.Count; index++)
    {
        MachineToolDeviceEntry device = devices[index];
        if (device.Kind != MachineToolDeviceKind.MachineTool)
        {
            continue;
        }
        MachineToolReadSnapshot snapshot = await client.ReadSnapshotAsync(device.NodeId, ct);
        Console.WriteLine($"{device.NodeId}: {snapshot.OperationMode.StatusCode}");
        if (snapshot.OperationMode.WrappedValue.TryGetValue(out int mode))
        {
            Console.WriteLine($"Observed mode {mode}, acquired {snapshot.OperationMode.SourceTimestamp}");
        }
    }
}

The caller still owns session. Disposing the facade cancels and drains its observations; it does not close the session. Use the returned kind to distinguish machines from wireless peripherals, rather than treating every discovered root as a MachineToolType.

Use a container-managed connection

Add AddMachineToolClient() to an existing AddOpcUa().AddClient(...) registration, then resolve MachineToolClientFactory. The equivalent Func<CancellationToken, Task<MachineToolClient>> is also registered.

The injected managed-session accessor caches a shared connection for the container. Factory-created facades borrow it, just like directly constructed facades. Disposing one facade, or cancellation immediately after acquisition, must not disconnect other consumers. The container disposes the shared session at shutdown; applications that create a session independently retain its ownership. See client hosting.

Server hosting and ownership

Standalone hosting

AddMachineTool(options => ...) selects one MachineryNodeManager. MachineToolServerOptions.InstanceNamespaceUri defaults to urn:opcua-netstandard:machinetool:instances and must be an absolute application URI, not a standard model namespace.

ConfigureMachineTool awaits a Func<IMachineryBuildContext, CancellationToken, ValueTask> after the model closure has loaded. Create multiple machines or peripherals in that callback, with distinct names, providers and stores. Registration and rollback belong to the shared owner; a handle belongs to one instance.

Compose into an existing owner

Do not call AddMachineTool() when another registration already owns the same DI/Machinery address space. Load the additional model through the existing pipeline and target that owner explicitly:

using Microsoft.Extensions.DependencyInjection;
using Opc.Ua.Machinery.Server;
using Opc.Ua.MachineTool;
using Opc.Ua.Server.Hosting;

static IOpcUaServerBuilder AddMachineToolModelsToCell(IOpcUaServerBuilder server)
{
    return server
        .AddMachinery(options =>
        {
            options.Parts = MachineryParts.BuildingBlocks |
                MachineryParts.Jobs | MachineryParts.ProcessValues;
            options.InstanceNamespaceUri = "urn:example:cell:instances";
        })
        .AddMachineToolToMachinery(options => options.Parts = MachineToolParts.All);
}

Here AddMachinery establishes the cell's owner; omit that call if it is already registered. Use ConfigureMachineToolFor<MachineryNodeManager>(...) to build on it. Shared composition uses the existing owner's application namespace, not a second namespace-owner registration.

GMS, cranes and Machine Vision can use the same owner when its required model closure and Machinery parts are present. The CompanionSuite sample demonstrates that composition without registering duplicate DeviceSet or Machines owners.

Custom owners and generated subtypes

ConfigureMachineToolFor<TNodeManager> targets an existing DiNodeManager subtype that supports the Machinery construction pipeline and node-ID allocation contracts. Loading model types and implementing the compatible owner are separate from running the configurator; see Machinery composition.

context.AddMachineTool<TState>(name, createState) accepts a generated MachineTool subtype. The factory must return an unpublished node with the requested identity. WithMachinery(...) configures that same typed Machinery builder; it must not independently build, register or dispose it. This is how GMS keeps its native root, result management and derived child types instead of being represented by a copied base machine.

MachineTool builder reference

Mandatory structure and monitoring

WithFeatures(MachineToolFeatures.None) is the default. Mandatory inherited identification, monitoring and retained active-program structure are not removed just because optional features are off.

Selection Provider data and behavior
Channels Ordinary and combined channels, execution/mode observations and override values; combined channels refer to real ordinary channels
SoftwareIdentification Installed software identities, separate from the overall machine revision
AdditionalIdentification Requires component name, model, construction year/month and device class on the nameplate
WorkingUnits Spindles, lasers and EDM generators with their reported modes and values
Stacklights Actual segmented IA stacklights, not a computed substitute for machine state
LegacyPowerOnDuration Retained deprecated integral-hours counter
OperationCounters DI/Machinery duration counter using the runtime's declared millisecond input
PartsProducedInLifetime Explicit lifetime production count, not a count inferred from reads
MachineryState Nested Machinery item/operation-mode observations
Obligation, MaintenanceMode Responsibility and maintenance substate; selections and parent states must be compatible

MachineToolSampleInfo carries acquisition time and quality. Feed overrides, mode values, counters and inventory references are validated before commit. An unsupported or missing value is not silently converted into a successful zero. Optional flags are capability selections, not permission to fabricate data.

WithMonitoringRequirements describes the deployment's monitoring needs for conformance evaluation. It does not increase server quotas or turn on optional features.

Tools, working units and life

WithEquipment(MachineToolEquipmentOptions) configures Tools, DynamicTools and ToolLife, with finite tool, element, text and per-tool life-entry limits. Tools and multi-tools retain their stable provider keys, identifiers, location, lock state and independent life measurements. Life values do not implicitly drive a physical tool change.

Dynamic tools use NodeVersion/model-change reporting and the shared final-value publication barrier. Removed equipment is not disposed while its final required observation is still waiting to enter a Publish response. Typed factories can create real generated companion subtypes and return their owned resources.

The options added by WithEquipment, WithProduction and WithPrognoses contribute feature flags. A later WithFeatures(...) replaces the combined selection, so configure it deliberately rather than unintentionally clearing an earlier module.

Retained Production

WithProduction(MachineToolProductionOptions) binds the retained Production model, including jobs, ordered programs, part sets, individual parts, statistics and interruption conditions. The provider supplies production phases and transition sequence numbers; Initializing, Running, Interrupted, Ended and Aborted are observations, not new execution methods.

Selection Effect
Production Retained jobs/programs and state/transition information
DynamicProductionPlan Insertion/removal using stable keys, publication barriers and resource cleanup
ProductionParts Part sets, parts and coherent quality counts
ProductionStatistics Retained UInt32 lifetime statistics
ProductionInterruptions Known interruption reasons as retained standard conditions

The default bounds include 64 jobs, 128 programs per job, 32 part sets per job, 1,024 parts per set and 4,096 aggregate elements. Select tighter limits in MachineToolProductionOptions when appropriate; there is no silent truncation. The runtime validates references, count consistency and permitted transitions before replacing the accepted snapshot.

Machinery and ISA-95 job management

The modern JobManagement AddIn is separate from retained Production. It lives under MachineryBuildingBlocks and uses the shared ISA-95 Job Control V2 binder. Complete MachineTool job management requires both IIsa95JobOrderReceiverV2 and IIsa95JobResponseProviderV2, plus a per-instance IIsa95JobOrderCatalog. A receiver-only configuration is rejected.

using Opc.Ua.MachineTool;
using Opc.Ua.MachineTool.Server;

static MachineToolBuilder<MachineToolState> BindJobSource(
    MachineToolBuilder<MachineToolState> machine,
    MachineToolJobSource source)
{
    return machine
        .WithProvider(source)
        .WithJobManagement(new MachineToolJobManagementOptions
        {
            Receiver = source,
            ResponseProvider = source,
            Catalog = source,
            PredefinedParameters = true
        })
        .WithProduction(new MachineToolProductionOptions
        {
            Features = MachineToolFeatures.Production |
                MachineToolFeatures.DynamicProductionPlan
        });
}

Construct the source with explicit MachineToolJobSourceOptions and job/program descriptors. MachineToolJobSource is a bounded, single-host, non-durable reference adapter linking accepted ISA-95 state with retained observations. It never executes CNC hardware. Keep it application-owned and dispose it after its runtimes have drained.

Standard verbs remain Store, StoreAndStart, Start, Stop, Abort, Pause, Resume, Clear, Cancel, Update and RevokeStart. Their UInt64 ReturnStatus is an ISA-95 bitmap: success is 1, not UA Good (zero). An accepted order is not evidence of physical execution. Providers describing different equipment must not be combined into one instance's control, response and monitoring views.

FileSystem and WorkMasters

WithFileSystem(IFileSystemProvider, includeWorkMasters, FileDirectoryBindingOptions) uses the existing file-system binder. It does not add another file node manager. When selected, WorkMasters must already exist as a directory in the provider and is exposed with the MachineTool-qualified browse name. An absent directory or inadequate materialization limits fail setup.

The provider owns storage and access decisions. Operation flags and MaxEntries/MaxDepth bound the shared directory binding. The runtime owns materialized nodes and session-bound file handles, not the application-supplied provider. Remote paths are scoped UA paths, not permission to read host-local files. Reading a program file does not execute it.

Prognoses and notifications

WithPrognoses(MachineToolPrognosisOptions) selects explicit SupportedKinds and the Prognoses/DynamicPrognoses features. The provider supplies predictions and actual affected-entity references; the library does not infer equipment forecasts. Limits cover prognosis count, referenced entities and text.

WithNotifications(MachineToolNotificationOptions) requires a separate IMachineToolNotificationProvider. It binds errors, alerts and messages through the existing alarm/event machinery, with bounded identities, message batches and refresh time. RefreshNotificationsAsync is separate from the ordinary equipment snapshot refresh.

The notification model carries retained conditions, enable/acknowledge/comment behavior and original event identities. Passive observation does not acknowledge or confirm conditions. Use Alarms and Conditions for the standard condition lifecycle rather than inventing machine-specific verbs.

Wireless peripheral server

Build an independent peripheral

Select MachineToolParts.Wmtp for a wireless-only endpoint, or All to host machines and peripherals together. Both use ConfigureMachineTool; inside the callback use AddWirelessPeripheral, not AddMachineTool.

using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Machinery.Server;
using Opc.Ua.MachineTool.Server;
using Opc.Ua.MachineTool.WMTP;

static ValueTask<WmtpPeripheral> BuildTemperaturePeripheralAsync(
    IMachineryBuildContext context,
    IWmtpDeviceProvider device,
    IWmtpMeasurementProvider measurements,
    IWmtpAuthorization authorization,
    EUInformation temperatureUnits,
    CancellationToken ct)
{
    return context.AddWirelessPeripheral("TemperatureProbe")
        .WithProvider(device)
        .WithMeasurementProvider(measurements)
        .WithAuthorization(authorization)
        .WithIdentification(identity =>
        {
            identity.Manufacturer = new LocalizedText("Example manufacturer");
            identity.SerialNumber = "Probe-001";
        })
        .AddMeasurement(new WmtpMeasurementDefinition
        {
            Name = "Temperature",
            Kind = WmtpMeasurementKind.Temperature,
            DataType = BuiltInType.Double,
            EngineeringUnits = temperatureUnits,
            EURange = new Opc.Ua.Range { Low = -20, High = 120 },
            InstrumentRange = new Opc.Ua.Range { Low = -20, High = 120 },
            ValuePrecision = 0.1,
            Definition = "Acquired probe temperature",
            TriggerMilliseconds = 1000,
            Delta = Variant.From(0d)
        })
        .BuildAsync(ct);
}

The nameplate is the existing component identification inside DeviceInformation. The explicit device adapter can also implement measurement, calibration, time or transfer interfaces; separate With...Provider calls override those interfaces for this peripheral.

No unkeyed device provider is chosen implicitly from DI. A shared authorization policy may be resolved from the construction context, but device selection remains explicit. Selected methods require both their provider and authorization policy before any peripheral is published.

Measurement kinds and acquired values

WmtpMeasurementKind covers all eight released kinds:

Kind Kind Kind Kind
Temperature RotationSpeed Humidity Acceleration
Force Torque Position Dimension

Each measurement is a generated WMTPMeasurementType composed with Machinery ProcessValues/PA-DIM, not a second signal implementation. Its definition supplies the exact numeric BuiltInType, units, engineering/instrument ranges, precision, sample category, trigger and delta. Distinct sensor names permit repeated kinds; the builder permits at most 256 sensors per peripheral.

IWmtpMeasurementProvider.ReadAsync(sensor, ct) returns WmtpMeasurementSample, including the original DataValue, device index and measurement period. A bad/null sample is unavailable data, not a new zero. StaleAfter controls when a usable acquisition becomes UncertainLastUsableValue; refreshing receipt time does not refresh the equipment's acquisition timestamp.

WmtpMeasurementFeatures Additional behavior
LimitCounters Status and duration above high/high-high with standard references
InternalUpdateInterval The sensor's own sampling interval
WorkCycleInformation Acquisition timestamp, index and measurement period
Uncertainty Absolute and relative uncertainty
LimitAlarm The shared ProcessValues limit alarm

Trigger and delta configuration

The mandatory SetTriggerSettings and SetDeltaCondition methods are provider-backed. Trigger is a whole-millisecond value representable by UInt32. Delta retains its numeric input type; incompatible, nonfinite, negative or unrepresentable values are refused rather than rounded into another type.

Trigger and delta must not both be zero. Each method passes both proposed settings to IWmtpMeasurementProvider.ConfigureAsync, so acceptance is atomic. Before acceptance, refusal or cancellation leaves the prior settings unchanged. After the provider accepts, a later caller cancellation does not discard that accepted configuration. A client whose request was interrupted must inspect the current settings instead of assuming rollback or retrying automatically.

Work and service records

WithWorkRecords(store, features) and WithServiceRecords(store, features) select independent datasets. The same store instance cannot back both. IWmtpRecordStore exposes count, complete-copy read and atomic delete; reads never consume records.

The basic dataset supplies abort, count and complete-report operations. WmtpRecordFeatures explicitly selects time counts, index/time reports, first/last reports and all/index/time deletion. Index and acquisition-time ranges are inclusive. First/last refer to storage order.

CombinedReportAll means the complete dataset, not an implicit page. If its encoded response exceeds the configured report budget, it fails. Use a supported explicit range; do not silently truncate the result. InMemoryWmtpRecordStore is a bounded reference store, not distributed durable storage. Work and service records remain separate even when they describe the same sensor.

Operation coordination is peripheral-scoped. An authorized AbortOperation may affect the peripheral's active work, so authorization receives both the calling session and active operation's session. A local cancellation or timeout never implicitly sends this method.

Calibration, device time and byte transfer

WmtpFeatures Required adapter or observation
Calibration IWmtpCalibrationProvider; observed calibration mode plus explicit mode request
DeviceTime IWmtpDeviceTimeProvider; UTC time with TimeZoneDataType offset/DST information
Firmware, License IWmtpTransferProvider; bounded byte acceptance, plus selected licence metadata
Battery Original battery ProcessValue sample
ServiceInformation Service dates and device-reported lifecycle status
AdvancedServiceInformation Basic service information and the specified lifetime/operation counters
WorkRecords, ServiceRecords The corresponding explicit record stores

WithByteLimits(maximumTransferBytes, maximumReportBytes) bounds the server payloads; transport/message quotas may impose a lower limit. The maximum configurable value is 64 MiB for each, with a minimum report budget of 256 bytes.

Setting device time does not change the server's TimeProvider. Calibration-mode acceptance does not prove a physical calibration completed. Firmware/licence transfer success means the provider accepted the bytes; it does not assert installation, restart, signature verification or licence activation. Those responsibilities belong to the application adapter and equipment.

Typed client workflows

Machine monitoring, production and services

API Result or role
DiscoverAsync Actual machine/peripheral roots, including vendor subtypes
ReadSnapshotAsync Monitoring values with raw quality/time and optional absence
ReadEquipmentAsync Tools/multi-tools, identities, locations, locks and life
ReadProductionAsync Retained jobs, programs, parts, statistics and transitions
ReadPrognosesAsync Predictions and their real target relationships
GetMachineProxy, GetPeripheralProxy Generated namespace-correct advanced proxies
GetJobManagementAsync Optional generated Machinery Jobs proxy
GetFileSystemAsync, GetWorkMastersAsync Optional standard directory proxies
GetWirelessPeripheral Typed WMTP workflow on the same session

Missing optional service AddIns return null where the accessor documents it. Missing mandatory structure, denied access and service failures remain errors. Discovering a namespace or root is not proof of every optional capability.

Use the generated job-role proxies below the returned Jobs object and the existing ISA-95 and file client workflows. Do not substitute a job-response receiver for the published response provider, or assume a same-named directory is the qualified WorkMasters node.

Bounded data and event observation

ObserveDataAsync observes a machine's monitoring/active-program values. ObserveValuesAsync accepts 1-256 distinct non-null native variable NodeIds, including WMTP signal nodes. Both create their own subscription and preserve source timestamps and status.

using System;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.MachineTool;
using Opc.Ua.MachineTool.Client;

static ValueTask<MachineToolDataObservation> ObserveMachineAsync(
    MachineToolClient client, NodeId machineId, CancellationToken ct)
{
    return client.ObserveDataAsync(machineId, new MachineToolObservationOptions
    {
        Window = TimeSpan.FromSeconds(5),
        MaximumNotifications = 20
    }, ct);
}

The observation window includes setup and cannot exceed 60 seconds. The maximum retained notification count is 100 and decoded payload is bounded. Results indicate count-limit completion and loss; an empty timed window is not proof of a particular machine state.

ObserveEventsAsync is passive. ObserveConditionsAsync explicitly requests standard retained-condition refresh and reports refresh completion. Neither implicitly acknowledges or confirms a condition. Generated event decoders and the shared event collector preserve concrete subtype identity and field order. See subscriptions for long-lived application subscriptions.

Wireless measurements and configuration

GetWirelessPeripheral(peripheralId) returns a WmtpPeripheralClient. ReadSnapshotAsync reads device metadata and ReadMeasurementsAsync reads the bounded native inventory. A measurement's NodeId is its configuration object; its ValueNodeId is the PA-DIM signal to observe.

The generated measurement proxy preserves the released method signatures:

using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.MachineTool.Client;
using Opc.Ua.MachineTool.WMTP;

static async ValueTask ConfigureWirelessSensorAsync(
    WmtpPeripheralClient peripheral, NodeId measurementId, CancellationToken ct)
{
    WMTPMeasurementTypeClient sensor = peripheral.GetMeasurementProxy(measurementId);
    await sensor.SetDeltaConditionAsync(Variant.From(2d), ct);
    await sensor.SetTriggerSettingsAsync(0, ct);
}

This is two explicit requests, not a client-side transaction. Each preserves the both-not-zero invariant independently; if the second fails, the first accepted change remains. Choose a delta type compatible with the actual sensor.

SetCalibrationAsync, SetDeviceTimeAsync and TransferAsync use generated configuration navigation. Transfers take bounded ByteString data, not a path or URL. They do not add installation or licensing semantics.

Wireless record queries

The wrapper normalizes the two released work/service proxy shapes while keeping the same standard operations:

using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.MachineTool.Client;
using Opc.Ua.MachineTool.WMTP;

static async ValueTask<ArrayOf<WMTPOutputDataType>> ReadWorkRecordsAsync(
    WmtpPeripheralClient peripheral, CancellationToken ct)
{
    WmtpRecordClient records = await peripheral.GetWorkRecordsAsync(ct);
    return await records.ReadAsync(new WmtpRecordQuery
    {
        Selection = WmtpRecordSelection.Index,
        FromIndex = 1,
        ToIndex = 20
    }, maximumRecords: 20, cancellationToken: ct);
}

The endpoint must select index reports for this example. Use GetServiceRecordsAsync for the other dataset. CountAsync supports all/time selections; ReadAsync supports all/index/time/first/last; DeleteAsync supports all/index/time. An absent optional method is not an empty successful result. ReadAsync rejects reports above the caller's bound; the default is 4,096. AbortAsync is always an explicit authorized request.

Lifetime, cancellation and failure handling

Server transactions and dynamic removal

MachineTool refresh separates acquisition/validation, coherent commit and publication. Rejected acquisition does not overwrite the previously accepted snapshot. A publication failure after acceptance is different: RetryPublicationAsync retries the retained values/events without reacquiring or repeating equipment effects.

Dynamic production, tools and prognoses use the shared final-value publication contract. It waits for the specific terminal queue entry to enter a real Publish response, not merely an equal older value or a queued notification. It does not claim network delivery or client acknowledgement. Failed/cancelled publication does not authorize removing the still-required subtree.

Callbacks must honor cancellation and avoid reentering their own runtime's acquisition/disposal path. Stop acquisition before shutting down the owning host. Disposal drains this instance's registrations and owned resources rather than touching another machine's provider or subscriptions.

Client and provider ownership

Resource Owner
Application-supplied MachineTool provider, job source, notification provider or file provider Application
WMTP providers/stores supplied normally Application
Resource passed to WmtpPeripheralBuilder.WithOwnedResource Peripheral only after successful construction; do not transfer a shared resource
Runtime-created nodes, subscriptions, metadata/file handles and registration resources Their existing runtime/operation owner
Session supplied to MachineToolClient, including one acquired by its factory Application or DI container

WMTP failed construction does not take ownership of caller resources. Intentionally shared device adapters must remain application-owned and coordinate their own equipment access.

Local cancellation ends the caller's wait and triggers call-owned cleanup. It does not prove a submitted remote write was rolled back, does not send a wireless Abort implicitly, and does not close a borrowed connection. Keep independent session shutdown in the code that actually owns the session.

MCP integration

Select or embed the bounded profile

The host profile is machinetool; there is no separate wmtp profile:

opcua-mcp --profile machinetool

For embedding:

using Microsoft.Extensions.DependencyInjection;
using Opc.Ua.Mcp;

static IMcpServerBuilder AddMachineToolMcp(IServiceCollection services)
{
    services.AddOpcUaMcpCore().AddOpcUaMcpMachineTool();
    return services.AddMcpServer()
        .WithStdioServerTransport()
        .WithOpcUaMcpFilters()
        .WithOpcUaMachineToolTools(McpToolProfile.MachineTool);
}

The profile-set overload supports composed catalogs and deduplicates common connection tools. Calls borrow the host's OpcUaSessionManager sessions; provide sessionName when more than one connected session is available.

Passive and explicitly mutating tools

Group Tools
Machine inventory/reads machinetool_list_devices, machinetool_read_machine, machinetool_read_equipment, machinetool_read_production, machinetool_read_prognoses
Observation machinetool_observe_values, machinetool_observe_machine, machinetool_observe_events
Jobs/files machinetool_query_jobs, machinetool_control_job, machinetool_list_files, machinetool_read_file, machinetool_change_file
Wireless reads wmtp_read_peripheral, wmtp_count_records, wmtp_report_records
Wireless mutations wmtp_configure_measurement, wmtp_configure_peripheral, wmtp_delete_records, wmtp_abort_operation, wmtp_transfer

Mutations require explicit confirm=true and remain subject to the remote server's authorization and provider refusal. Reads do not start jobs, change settings, consume records or acknowledge conditions. Native job return bitmaps, UA service status and firmware/licence acceptance are not interchangeable.

Authorization, budgets and cancellation

Profile selection controls the catalog, not authority. The host's privileged/live capabilities are independently default-off; enabling a capability also requires appropriate caller grants and network rules. Confirmation flags do not replace those controls or OPC UA permissions. See MCP execution policy.

Wireless deadlines are 1-60 seconds; complete replies default to 128 KiB and cap at 1 MiB. Wireless inventories cap at 256 measurements, reports at 4,096 records, and inline transfer data at 1 MiB decoded. Native client/server limits may be tighter. Machine job/file tools have their own schema limits; file content is bounded separately from base64/JSON expansion.

Oversize complete results fail rather than silently truncate. Observations report their bound/loss information and release only their own resources. MCP cancellation must reach the server, for example through the standard notifications/cancelled notification; abandoning a local client wait is not necessarily a remote cancellation.

The MCP package reference documents exact selectors, typed numeric unions, job JSON, scoped remote paths and byte limits. Use the live schemas rather than inventing additional wireless or CNC verbs.

Conformance and support limits

Supported surfaces

Feature Support
MachineTool 1.02.0 and bundled WMTP 1.0.0 Released models; independent runtime selection
Monitoring, channels, working units, tools/life, prognoses and conditions Explicit provider-backed observations
Retained Production, states/parts/statistics, Machinery/ISA-95 jobs and remote files Implemented through native types and shared services
All eight WMTP measurement kinds and checked trigger/delta settings Original type, quality and acquisition time retained
Battery/service metadata, calibration mode and UTC/time-zone requests Selected observations/providers required
Independent work/service record operations Complete-copy reads, explicit range/delete/abort
Firmware/licence bytes Authorized provider acceptance only
Standalone/shared hosting, isolated instances, typed clients, MCP and NativeAOT Same model/runtime APIs; deployment-specific configuration still required
Separate WMTP package/profile/guide; CNC, Bluetooth/pairing, flash/licensing or safety drivers Not supplied
External OPC Foundation certification of a deployment Not claimed

Runtime claims and deployment requirements

Model exposure and operational conformance are different. Contributors report capabilities of committed live instances and selected providers; namespace presence alone cannot establish a monitoring, job, file or record facet. Security-dependent claims also require actual endpoint policies, certificates and user-token support. Configuring a monitoring requirement does not make a server quota or equipment update rate true.

Facet identities follow the released MachineTool profiles and WMTP profiles. See Profiles for repository-wide scope and certification limits. The executable evidence is maintained in the MachineTool tests, including live-session, publication, provider-refusal and lifetime regressions; support tables are not a certification result for a particular device.

Model provenance

Both models are pinned to OPC Foundation UA-Nodeset revision 96a08945425d0295edb3607d2a38f6841ef44046. Original filenames, including WMTP's Nodeset2 spelling, are preserved:

Local model Purpose
Opc.Ua.MachineTool.NodeSet2.xml Normative MachineTool wire identities
Opc.Ua.WMTP.Nodeset2.xml Normative WMTP wire identities
*.Derived.NodeIds.csv beside each model Locally derived generator drift guards, not replacement numeric identifiers
MachineToolModels Pinned source commit, namespace closure and LF-normalized NodeSet SHA-256 values

The generated state/proxy types consume dependency payloads from their existing model assemblies. No second copy of DI, Machinery, ISA-95 or PA-DIM is generated inside MachineTool. See model dependencies for the cross-assembly mechanism and Machinery provenance for identifier-table conventions.