# Description This completes the AutoID contribution proposed in #4824: the model package for [OPC 30010 OPC UA for AutoID Devices](https://reference.opcfoundation.org/AutoID/v101/docs/) (1.01) plus the server, the client, an MCP profile and a sample pair, in one PR. It follows the newest companion pattern (GMS #4765, MachineTool, CranesHoists, MachineVision). ## Commits 1. `fix(sourcegen)`: a structure property whose DataType a subtype refines is redeclared with its own field (the NodeSet needs it; unchanged from the first slice). 2. `feat(autoid)` model package `Opc.Ua.AutoID` (unchanged from the first slice). Adds only `AutoIdReaderKind`. 3. `feat(autoid)` server package `Opc.Ua.AutoID.Server`. 4. `feat(autoid)` client package `Opc.Ua.AutoID.Client`. 5. `feat(autoid)` MCP profile `Opc.Ua.Mcp.AutoID`. 6. `feat(autoid)` samples `samples/AutoID`. 7. `build(autoid)` solution, package list and package validation tests. 8. `fix(autoid)` x3, `docs(autoid)` and `test(autoid)`: review fixes, net48 compatibility, documentation, the native AOT test. 9. `fix(autoid)` x3 and `feat(autoid-client)`: `GetLocation` location types, the `Client` of access events, the default scan cycle of the simulated readers, and NodeIds and methods in the device snapshot (details below). 10. `feat(autoid)`, `test(autoid)`, `docs(autoid)` (`afdd7858b`, `22bc3b26e`, `ed25fe4ae`, on top of the master merge `1c7948f65`): AutoID composes into an existing Device Integration owner like Pumps and Scales (review feedback; see "Composition with other DI-based companions"). 11. `1458af9e9` (marcschier): the `AutoIdReadOptions` remarks state that `StatusOnly` limits the values read and that each call still browses (Copilot review). 12. `fix(autoid)` `3ef2f1507`: `GetDeviceLocation` answers with the same status codes as `GetLocation`; `feat(autoid-mcp)` `f6405152f`: `autoid_read_device` shows the new snapshot NodeIds and methods (details below). ## Server: `Opc.Ua.AutoID.Server` - Reader devices (RFID, OCR, optical, optical verifier, RTLS) on the DI device model, created by a fluent builder (`AutoIdRuntime.Reader(name, provider)`) and hosted with `AddAutoId` / `ConfigureAutoId` on `IServiceCollection`. - Hardware is behind provider interfaces: `IAutoIdReaderProvider`, `IAutoIdScanProvider` (Scan), `IAutoIdAsyncScanProvider` (ScanStart/ScanStop), `IAutoIdRfidProvider` (ReadTag, WriteTag, WriteTagID, KillTag, LockTag, SetTagPassword, each opt-in by flag), `IAutoIdRtlsProvider`. Providers push scans, presence, log entries and access events through `IAutoIdReaderSink`. In-memory providers (an EPC Gen2 tag model with banks, passwords, locks and kill) are included for tests and samples. - Optional children (methods, `LastScanData`, `DeviceLocation`, `AntennaNames`, location methods) exist only when the provider supports them. Facet profiles and conformance units are advertised only for what is implemented (`IConformanceContributor`); the Full facet is withheld. - `GetLocation` (OPC 30010 6.6.3.2) returns the location in the requested `LocationType`. An unsupported type is `Bad_InvalidArgument`, an unknown object `Bad_NotFound`, and a supported type the object's location is not available in `Bad_InvalidState` (6.6.3.2: "not available or not allowed e.g. due to special configuration"); a provider result of another type never reaches the client. `InMemoryRtlsProvider` takes an optional location converter, for example from the configured local coordinate system (9.3.4) into WGS84. - `GetDeviceLocation` (6.1.3.7) uses the same mapping: an undefined location type is `Bad_InvalidArgument`, a device location that is not available in the requested type `Bad_InvalidState` (it answered `Bad_NotSupported` before). 6.1.3.7 lists only "Standard OPC UA status codes defined for the Call Service in OPC 10000-4"; there `Bad_NotSupported` means "The Method is not supported for the Object instance", which is wrong for a published method, and OPC 10000-4 5.12.2.4 allows reusing an existing status code with the same canonical meaning, so the method follows 6.6.3.2. - Access events (7.10, 7.11): `Client` is "the name of the client application which was the originator of the command", so it is the application name of the session's client description (else its application URI), not the user identity. The results of a `ScanStart` scan name the client that started it; scans the device reports on its own name the device. - The simulated readers default to one scan cycle per second (`CycleInterval`, configurable per provider; the length of a cycle is device specific in OPC 30010), so an unbounded `ScanStart` raises at most one scan event per second. - Behaviour: one scan at a time with a generation counter; ScanStart/ScanStop serialised; a scan started by a session stops when that session closes; the maximum scan duration, tag read length and result count are limited by `AutoIdServerOptions`; device status changes and events are raised outside locks; registration is rolled back on failure; readers, providers and the runtime are disposed in order and in-flight operations are drained. ## Composition with other DI-based companions A server has one Device Integration owner, and AutoID now uses the existing harmonized composition instead of requiring its own node manager: - `AutoIdMachineryModelProvider` adds the AutoID model to the Machinery owner's model-provider pipeline (dependency-ordered, `Order` 200) and keeps the DI nodes the owner loaded, so DI and `DeviceSet` exist once. - `AddAutoIdToMachinery` (after `AddMachinery`) registers it; `ConfigureAutoIdFor<TNodeManager>` creates readers on any `DiNodeManager` owner, successive delegates share one `AutoIdRuntime`; `owner.CreateAutoIdRuntime()` is the path without DI hosting. The owner disposes the runtime on shutdown. - Readers are registered in the owner below its `DeviceSet`, NodeIds in the owner's instance namespace (`AutoIdRuntime.InstanceNamespaceIndex`), not in the `Machines` folder; the AutoID conformance units and facets are published by the owner next to the DI and Machinery ones. - `AddAutoId` remains for the "AutoID is the only DI-based companion" case and is built from the same provider. Next to another DI owner it is still rejected; the message names the composition API. (Bypassing that check gives two `DeviceSet` copies; the server routes `DeviceSet` to the first owner and readers in the second are not browsable, checked locally.) - `Opc.Ua.AutoID.Server` now references `Opc.Ua.Machinery.Server` (a stable package), as Vision does; a stand-alone server still loads only DI and AutoID. ## Client: `Opc.Ua.AutoID.Client` Reader discovery (a failed browse propagates), typed calls for every published method, a device snapshot with documented `AutoIdReadOptions`, and an async event stream (`ObserveScan/Access/Presence/LogEntries`) on `IStreamingSubscription`. Cancellation is honoured throughout. `AddAutoIdClient` registers it for DI. Because most children of a device are optional (6.1, 6.5.3, 6.6.3), the snapshot also carries the NodeIds of `DeviceStatus`, `LastScanData`, `LastScanTimestamp` and `DeviceLocation` and the browse names of the published methods (`Methods`, `HasMethod`), taken from the same browse whatever the read options, so a client can monitor the device and knows what it may call without browsing again. ## MCP profile: `Opc.Ua.Mcp.AutoID` `McpToolProfile.AutoId` with nine tools: list readers, read device, scan, scan start/stop, observe scan events, read tag, get supported location types, get location. `autoid_read_device` also returns `deviceStatusId`, `lastScanDataId`, `lastScanTimestampId` and `deviceLocationId` (NodeId strings, null when the device does not publish the variable) and `methods` (the browse names of the published methods), built explicitly without reflection. Registered in the host builder, the profile switches of the other profiles, `Test-McpGrade.ps1`, `McpREADME.md` and `.mcp/server.json`. ## Samples `samples/AutoID/AutoIdServer` (a dock-door reader with a simulated pallet) and `AutoIdClient` with a README and `Test-AutoIdSamplePair.ps1`. The default port is not a common one; tests use port 0. ## Other changes - `Opc.Ua.Di.Server` gets `InternalsVisibleTo` for `Opc.Ua.AutoID.Server` (`RegisterRootNotifierAsync`). - `UA.slnx`, `.azurepipelines/expected-packages.txt`, `PackageVersionPolicyTests`, `NuGetPackageSetValidationTests`. - Docs: `docs/AutoID.md`, `docs/McpServer.md`, `docs/Profiles.md`, `docs/Samples.md`, `docs/DeveloperGuide.md`, `docs/README.md`, `docs/WhatsNewIn2.0.md`, `README.md`, NugetREADMEs. The "model package only" statements are removed. - All packages follow the root version (no preview override). ## Specification notes and questions for the maintainers - The model is 1.01, published 2020-06-18 (the NodeSet header). OPC 30010 6.1.3.16 prescribes the `AutoIdModelVersion` string `"1.00"`, which is what the NodeSet carries and what the server publishes. - **Question:** the NodeSet's optical verifier `Scan` method returns `WGS84Coordinate`, which looks like a model error. It is not published, and providers that implement `IAutoIdScanProvider` for a verifier are rejected. Is that the behaviour you want? - The stack answers a call to an unpublished optional method with `BadNotImplemented`. ## Design choices reviewers may want changed - `Opc.Ua.Di.Server` gets `InternalsVisibleTo` for `Opc.Ua.AutoID.Server`, so the server can call `RegisterRootNotifierAsync`. - `AutoIdReaderKind` (and `AutoIdReaderKinds.TypeIdOf`) lives in the model package `Opc.Ua.AutoID`, not in the server package, so the client and the MCP tools share it. - The MCP profile is read-only/safe: no tools that write, kill or lock tags. ## Tests `tests/Opc.Ua.AutoID.Tests` has 306 tests (116 of the model slice, 190 new): reader lifecycle, scan semantics, tag access handlers, limits, conformance, in-memory providers, hosting, and over-the-wire client/server tests with result codes, events, concurrency and dispose. `tests/Opc.Ua.Tools.Tests` has 13 AutoID MCP tests plus the updated profile-count tests. New behaviour tests fail without the code (for example a provider's own cancellation, a synchronous scan while a scan stop is in progress; for the last round: `GetLocation` of a supported type the object is not located in answered `Bad_NotFound`, access events named the user "Anonymous" and attributed `ScanStart` results to the device, and an unbounded `ScanStart` on a default simulated reader reported 20 times in 400 ms). Composition: `AutoIdCompositionTests` (stock Machinery owner, providers out of order, DI loaded once, instance ownership, rollback, cancellation, runtime disposal, an application-owned DI owner, a second DI owner rejected) and `AutoIdMachineryCompositionEndToEndTests` (Machinery + Pumps + AutoID in one hosted server, read back by the AutoID, Machinery and Pumps clients, including a scan and the published profiles and conformance units). Mutating the change (no lifetime registration, the DI instance namespace for browse names, the old ownership message) fails three of them. The sample pair script passes. `tests/Opc.Ua.Aot.Tests.Mcp` has an AutoID profile test (`AutoIdMcpAotTests`); the project was published with Native AOT on osx-arm64 and all 64 tests passed. Publishing it also found two `JsonArray.Add<T>` uses in the MCP tools (IL2026/IL3050), now fixed. ## Verification (local, macOS arm64, SDK pinned in `global.json`) The branch contains the master merges `fc556be7e`, `1c7948f65` and `af7feb886`; the three composition commits sit between the last two (the latest merge touches no AutoID, DI or Machinery file). `1458af9e9` (marcschier) and the two commits of the last round follow `af7feb886`. - **Last round (`3ef2f1507`, `f6405152f`).** Builds with `-m:2 -nodeReuse:false -p:UseSharedCompilation=false`: `Opc.Ua.AutoID.Tests` on all target frameworks and with `-p:CustomTestTarget=net48`, `Opc.Ua.Mcp.AutoID` on net8.0/net9.0/net10.0, `Opc.Ua.Tools.Tests` on net10.0: 0 warnings, 0 errors. `Opc.Ua.AutoID.Tests` 306/306 on net10.0; `Opc.Ua.Tools.Tests` AutoID tests 60/60, and the profile, catalogue, composition and package tests that reference AutoID (2241) pass. The changed tests fail without the code: `GetDeviceLocation` answered `Bad_NotSupported` for both cases, and `autoid_read_device` had no `deviceStatusId`. `tests/Opc.Ua.Aot.Tests.Mcp` published with Native AOT on osx-arm64 (no IL2026/IL3050 warning): 64/64 passed. `dotnet format` whitespace, style and analyzers `--verify-no-changes` on the changed files: no finding on a changed line (line endings aside, which the LF checkout on macOS reports for every file). - **Build (composition round).** `Opc.Ua.AutoID.Server` on all target frameworks, `Opc.Ua.AutoID.Tests` on net10.0 and with `-p:CustomTestTarget=net48`, and the AutoID server sample: 0 warnings, 0 errors. MCP tools and JSON unchanged, so the native AOT test was not republished. - **Build (earlier rounds).** The AutoID packages and `Opc.Ua.AutoID.Tests` on net10.0 and, with `-p:CustomTestTarget=net48`, on net48/net8.0/net9.0/net10.0; `Opc.Ua.Tools.Tests` and `Opc.Ua.SourceGeneration.Core.Tests` on net10.0 (`-m:2 -nodeReuse:false -p:UseSharedCompilation=false`): 0 warnings, 0 errors. - **Tests.** `Opc.Ua.AutoID.Tests` 306/306 on net10.0 (composition round; earlier round 288/288); `Opc.Ua.Tools.Tests` (AutoID, host builder, profile and package tests) 2438/2438 on net10.0; `Opc.Ua.SourceGeneration.Core.Tests` 4358 passed, 8 skipped, after the master merge touched `NodeStateGenerator.cs`. net48 was built, not run (no Mono on macOS); the fork CI runs it. The MCP tools and their JSON are unchanged in the last round, so the native AOT test was not republished. - **Coverage.** Composition round: the local coverlet collector recorded zero hits for every module in this environment (also for unchanged code), so no new local number; the new paths are exercised by the 18 composition tests listed above and the fork CI uploads coverage. Earlier round: cobertura from the AutoID tests: `Opc.Ua.AutoID.Server` 92.0 % lines / 86.8 % branches, `Opc.Ua.AutoID.Client` 89.9 % / 80 %; 110 of the 112 coverable changed lines of the last round are covered (the two missed lines skip a browse result without NodeId or name). The generated code of the model package is excluded as in the other model packages. - **Format.** `dotnet format` whitespace, style and analyzers (`--severity info`) `--verify-no-changes` on the changed files: clean. Fork CI (full matrix including macOS): [run 39162077465](https://github.com/biancode/UA-.NETStandard/actions/runs/39162077465) on `f6405152f`: green. (Previous round: run 39130154771 on `ed25fe4ae`, 90/90 jobs green; the upstream checks on the merge `af7feb886` green, 108 passed, 5 skipped.) CodeQL: [run 39162079826](https://github.com/biancode/UA-.NETStandard/actions/runs/39162079826) on `f6405152f`: green, no alert in a file this PR touches. (Previous round: run 39130156598 on `ed25fe4ae`, green.) ## Related Issues - Part of #4824 (tracking issue: AutoID companion SDK). ## Checklist - [x] 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. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. (The solution builds on all TFMs; the AutoID test projects ran on net10.0 locally, net48 runs in the fork CI.) - [x] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
7.8 KiB
OPC Foundation UA .NET Standard Reference Server
Additional samples
- MachineTool and wireless peripherals — independently selected machine/peripheral models, per-instance simulation, typed clients, jobs/files, measurements and record/configuration workflows.
- GMS — native systems, environmental warnings and result/characteristic workflows with explicitly simulated providers.
- AutoID — a simulated OPC 30010 RFID reader: synchronous and asynchronous scans, tag memory access and presence events.
- Cranes and Hoists — observed crane topology and authorized simulated limit requests, never physical motion commands.
- Combined companion suite — MachineTool, WMTP, GMS, CranesHoists and MachineVision on one DI/Machinery owner.
- OPC 10000-21 onboarding registrar/client — loads the generated Onboarding companion model, binds the standard registrar administration methods to an injected ticket store, and drives a secure register/unregister round trip with a PowerShell script.
Introduction
This document is referring to the CTT version 1.04.9.398.
This OPC Server is designed to be the default OPC UA Server when opening the OPC UA Compliance Test Tool and it uses an address-space that matches the design of the UACTT and the requirements for OPC UA compliance testing.
It uses the OPC Foundation UA .NET Standard Library. Therefore it supports both the opc.tcp and https transports. There is a .NET framework based server with UI and a .NET Core console version of the server which runs on any OS supporting .NET 8.0 and later.
How to build and run the Windows OPC UA Reference Server with UACTT
- Open the solution UA.slnx with Visual Studio 2026.
- Choose the project
Reference Serverin the Solution Explorer and set it with a right click asStartup Project. - Hit
F5to build and execute the sample.
How to build and run the console OPC UA Reference Server on Windows, Linux and iOS
This section describes how to run the ConsoleReferenceServer.
Please follow instructions in this article to setup the dotnet command line environment for your platform.
Start the server
- Open a command prompt.
- Navigate to the folder samples/Reference/ConsoleReferenceServer.
- To run the server sample type
dotnet run --project ConsoleReferenceServer.csproj. The server is now running and waiting for the connection of the UACTT.
UACTT test certificates
The reference server always rejects new client certificates and requires that the UACTT certificates are in appropriate folders.
- The console server certificates are stored in %LocalApplicationData%/OPC Foundation/pki.
- The Windows .NET framework server stores the certificates in %CommonApplicationData%\OPC Foundation\pki.
- %CommonApplicationData% maps to the path set by the environment variable ProgramData on Windows.
- %LocalApplicationData% maps to a hidden location in the user home folder and depends on the target platform. See Certificates for more information.
Certificate stores
Under pki, the following stores contain certificates under certs, CRLs under crl or private keys under private.
- own contains the reference server public certificate and private key.
- rejected contains the rejected client certificates. To trust a client certificate, copy the rejected certificate to the trusted/certs folder.
- trusted contains trusted client and CAs certificates and CRLs.
- issuer contains CAs certificates and CRLs needed for validation.
- trustedUser contains trusted user and user CA certificates and CRLs.
- issuerUser contains user CA certificates and CRLs needed for validation of the certificate chains.
Placing the UACTT certificates
CTT creates a PKI folder in a new project. There is a subfolder copyToServer which contains the application and user identity certificates. Copy the ApplicationInstance_PKI certificates for testing with the UACTT to the following stores:
- trusted/certs: trusted certificates
- trusted/crl: revocation lists of trusted certificates
- issuer/certs: issuer certificates
- issuer/crl: revocation lists of issuer certificates
Copy the X509UserIdentity_PKI certificates for testing with the UACTT to the following stores:
- trustedUser/certs: trusted user certificates
- trustedUser/crl: revocation lists of trusted user certificates
- issuerUser/certs: user issuer certificates
- issuerUser/crl: revocation lists of user issuer certificates
UACTT Testing
Download and install the OPC UA Compliance Test Tool.
Note: Access to the UACTT is granted to OPC Foundation Corporate Members.
UACTT sample configuration
A sample configuration for the UACTT Version 1.04.9.398 can be found in UAReferenceServer.ctt.xml. Originally the reference server was certified only against the Standard UA Server profile, the Method Server Facet and the DataAccess Server Facet, in addition to all security related profiles. During release testing all Conformance Units should be enabled / checked.
Enabling Alarms & Conditions
The address space for Alarms contains a Start method which needs to be called to produce alarms for CTT testing. The method is called from a connected client.
The ConsoleReferenceServer has also a command line option -ctt which starts the Alarms when the server is started. The following Alarm instances are available for monitoring:
AlarmConditionTypeDiscrepancyAlarmTypeLimitAlarmTypeExclusiveLimitAlarmTypeExclusiveDeviationAlarmTypeExclusiveRateOfChangeAlarmTypeNonExclusiveLimitAlarmTypeNonExclusiveDeviationAlarmTypeNonExclusiveRateOfChangeAlarmTypeDiscreteAlarmTypeSystemOffNormalAlarmTypeExclusiveLevelAlarmTypeNonExclusiveLevelAlarmTypeOffNormalAlarmType
Known missing Tests and Issues
Depending on the setup in the provided project configuration, unsupported profiles should be skipped. At the time of writing (1.4.368) the following tests, profiles or facets are not yet covered or not configured due to issues:
- no support for events, historian and only a few basic Alarms & Conditions.
- Decimal type causes crash of CTT (needs investigation)
- no Image / Enumeration scalar test cases
- Arrays may not include Variant (needs investigation)
- no ArrayItemType implemented
- multiple hierarchical references test node missing
- deprecated security profiles support for SHA1/1k RSA keys implemented not according to certification. SHA1/1k keys can only be turned on/off for all profiles, not just for the deprecated profiles.
- Monitored Items with array are failing a few test cases.
It is recommended to run the server as retail build with disabled logging, to avoid side effects due to timing artifacts when log entries are written to a disk drive.
Running the CTT against a Docker container
Please refer to the Docker container of the Reference Server documentation page. Once setup to map the certificate store and the configuration to a local folder, follow the instructions above to copy the certificates to the PKI and get started.
Finding the Address Space Configuration Code
- Project: Reference Server
- File: ReferenceNodeManager.cs
- Method: CreateAddressSpace
Finding the UA Services
- Project: Opc.Ua.Server
- File: StandardServer.cs