# 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.
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
- Models and dependencies
- Quick start - server
- Quick start - client
- Server hosting and ownership
- MachineTool builder reference
- Wireless peripheral server
- Typed client workflows
- Lifetime, cancellation and failure handling
- MCP integration
- Conformance and support limits
- Model provenance
- Samples and related documentation
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.
Samples and related documentation
- MachineTool/WMTP sample pair: machine-only, wireless-only and mixed modes; mandatory/advanced selections; bounded, encrypted loopback examples and explicit accepted/refused requests.
- CompanionSuite: all families on one DI/Machinery owner with isolated instances.
- Machinery, Device Integration, ISA-95, GMS and Machine Vision.
- Node manager lifetimes, state machines, alarms and conditions, file services and model-change tracking.
- MCP server and the MachineTool MCP package.
- Developer guide for framework-specific builds and tests.