mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
# Description Implements the **Asset Administration Shell** companion specifications on top of `marcschier/wot-type-binding`, in two halves that a Server chooses between: * **AAS V3** — the [draft](https://github.com/marcschier/opcua-drafts/tree/main/companion-specs/AAS) (`http://opcfoundation.org/UA/I4AAS/v3/`), all sixteen conformance units: the metamodel, the registry built on the abstract xRegistry base, generational materialization, federation, packages with digest-bound integrity, the Digital Product Passport vocabulary, and the Annex F bridge to WoT Thing Descriptions. * **AAS V2** — the published [OPC 30270](https://reference.opcfoundation.org/specs/OPC-30270/full) (`http://opcfoundation.org/UA/I4AAS/`), which maps the AAS V2.0.1 metamodel: object model, ingestion of XML, JSON and AASX, materialization, server and client. The two are different metamodel generations rather than two revisions of one model — V2 keeps `Asset` as a first-class Identifiable, `identification.idType`, Views and ordered collections, none of which survive into V3 — so they are **alternatives**. A Server hosts one or the other; this is documented rather than enforced at startup. Each is named for the generation it maps (`Opc.Ua.Aas.V2`, `Opc.Ua.Aas.V3`, `AddAasV2Server`, `AddAasV3Server`), and what genuinely serves both carries no marker and sits at the root of `Opc.Ua.Aas`: the deterministic NodeId encoding and idShortPath conventions, `AasOptional<T>`, the digest rule, the materialization diagnostics, the AASX container walk, and the value and operation providers, which are keyed on `NodeId` and `Variant` and so carry no version at all. ## What is in here **New libraries**: `Opc.Ua.Aas`, `Opc.Ua.Aas.Server`, `Opc.Ua.Aas.Client`, `Opc.Ua.Aas.WoT`, plus `samples/Aas` and `docs/Aas.md`. **Enabling changes to existing code**, each needed by the above: * The OPC UA `Decimal` DataType added to `Opc.Ua.Types`, with its Part 6 §5.1.10 wire encoding. `BinaryDecoder` gained one internal member, `TryReadRemainingBodyBytes`, because a trailing field with no self-describing length cannot otherwise be read; `Decimal.Decode` is its only caller. Part 6 Table 3 types the unscaled `Value` as an **OctetString whose byte count is `Length` − 2**, not a length-prefixed `ByteString`, so it carries no length of its own — `DecimalEncodingTests` pins the body byte-for-byte against that mistake, and against the `Length ≤ 2` values the same table calls invalid. * The xRegistry projection engine extracted into `Opc.Ua.XRegistry.Server` and `Opc.Ua.WotCon.Server` migrated onto it (−848/+387 lines), with the WoT Connectivity tests passing **unedited**. * Two source-generator fixes, both of which made a model impossible to build (see below). ## Defects found and fixed along the way Running the stack end to end, rather than trusting per-area test suites, surfaced defects that no unit test caught. The ones worth a maintainer's attention: * **The generator emitted non-terminating constructors.** A structure field with no declared default was initialized with `new T()`, so a self-referential DataType (`AASReferenceDataType.referredSemanticId`) recursed until the stack overflowed and `AddOpcUaAas` could not load the model at all. The test host *died* rather than failing. Only the field that closes a cycle is now left null. * **The generator omitted `new` on a redeclared placeholder.** `AASOrderedSubmodelElementCollectionType` redeclares the `SubmodelElement` slot of its base type, and the generated `Add` method hid the base one without saying so, so the V2 model would not compile. No existing model could have hit either defect — both are build failures, so anything that builds today is unaffected. * **The metamodel half had never worked outside its unit tests**: the emitted NodeSet declared no reference-type aliases and was rejected by the loader; the Mandatory `Invoke` Method was never emitted, because a NodeSet is imported as written rather than instantiated from its type; the runtime bound at the authored namespace index instead of the one the Server assigns; and `MethodDeclarationId` was addressed in the wrong namespace. Every materializer test inspected the in-memory object graph, which cannot see whether the graph is loadable — two tests now import it exactly as `RuntimeNodeSetNodeManager` does. * **The client could not see any Property member.** It browsed `HasComponent` with subtypes, but `HasProperty` is a sibling under `Aggregates`, so reading a value failed and invoking failed argument validation. * **Every repeated registry write forked a duplicate group and resource** instead of adding a version, because the identifier was re-derived against a sibling set that already contained the entity itself. This also made the persistence bounds unreachable. * `.gitignore`'s `**/packages/*` silently swallowed the whole package-integrity implementation and its tests, which would have vanished on a fresh clone. ## Specification findings Nine findings against the V3 draft are fixed in [marcschier/opcua-drafts#75](https://github.com/marcschier/opcua-drafts/pull/75). The substantive one: clause 6.1.5's absent-versus-empty rule was **unsatisfiable** for five container fields that materialize as parent components with no node of their own, which made `AAS-LosslessRoundTrip` unachievable. OPC 30270 contradicts itself twice. Both are recorded in `AasV2ConformanceUnits.cs` and the documentation rather than smoothed over: Table 83 spells the unit `I4AAS MultiLangaugeProperty` where Table 85 spells it correctly, and Table 85 lists an `I4AAS Security` unit that Table 83 never defines. Table 84, which would assign profile URIs, is empty, so `ServerProfileArray` gains no I4AAS entry. ## Known limitation Calling an `Operation` **with arguments** answers `BadTooManyArguments`. Method resolution works from both the instance NodeId and the `MethodDeclarationId`, and the argument definitions are published, but `NodeState.AddChild` does not route through `MethodState.FindChild`, so a Method imported from a NodeSet leaves its typed `InputArguments` null. Closing this means changing how the shared NodeSet import claims typed children, which affects every consumer of runtime NodeSets and is out of scope here. It is documented in `docs/Aas.md` §11, the conformance matrix marks `AAS-OperationInvoke` partial rather than claiming it, and `samples/Aas` exercises the path and reports the status rather than working around it. V2 is unaffected — its `Operation` is an OptionalPlaceholder with no declared arguments. ## Related Issues - No tracking issue yet. Opening as a **draft** for design discussion first, per the ADR guidance below; happy to open one and link it once the shape is agreed. ## 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. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. ### Verification | Check | Result | |---|---| | AAS tests | **670 passed, 0 failed** on net472, net48, net8.0, net9.0, net10.0 | | Coverage | Client 80.8 %, Server 81.2 %, `Opc.Ua.Aas` 82.0 %, WoT 82.4 % — all above the 80 % gate | | Libraries | `Opc.Ua.Aas`, `.Server`, `.Client`, `.WoT` build clean on every target framework, zero warnings | | Regression | `Opc.Ua.Types` 8677, `Opc.Ua.WotCon` 1094, `Opc.Ua.XRegistry` 198, `Opc.Ua.Server` 4050 — all green | | Sample | `samples/Aas` builds warning-free and runs end to end | | Merge validation | Solution builds clean on net10.0 and net48; WoT Types 504, WotCon 1111, AAS 670, XRegistry 198 tests passed | | CI | All 99 checks green on `ff9298e44`; only the CLA signature status is outstanding | ### Review pass A full review against both specifications, and for API consistency and correctness, produced twelve fixes. Each one has a test that fails without it. | Defect | Where | Why it mattered | |---|---|---| | V2 projection aborted on **every** `File` read from a document | `AasV2EnvironmentRuntime` | The runtime bound `File/Value` unconditionally, the materializer emitted it only with content, and the builder throws `BadNotFound` for an unknown NodeId — aborting the whole registration. No reader ever populates `AasFile.File`, so no AAS V2 document could be hosted at all. | | `aas-suppl` anchored on the wrong part | `AasxPackageReaderCore`, `AasxPackageWriter` | IDTA and the Package Explorer anchor it on the environment part; we used the origin part. Every real `.aasx` returned zero supplementary files, silently. Affects both halves. | | Federation failed closed on pooled connections | `AasFederation` | `ConnectCallback` fires only for new connections, so the second fetch to an origin — and all of net472/net48/netstandard2.1 — failed with a security-flavoured error. The peer is now validated *inside* the callback, which makes reuse safe by construction. | | Malformed `Decimal` escaped as `ArgumentOutOfRangeException` | `BinaryDecoder` | A declared body shorter than `Scale` produced a negative read length. Two of the three test cases had been passing for the wrong reason. | | V2 and V3 cross-wired their value providers | `OpcUaAasServerBuilderExtensions` | Both registered `IAasValueProvider`; whichever was added first served both AddressSpaces. V2 now has its own contract, so co-hosting is harmless rather than silently wrong. `EnvironmentFolder` was also bound and ignored on the V2 path. | | Export filter skipped three nesting kinds | `AasEnvironmentExporter` | Content under `Entity`, `AnnotatedRelationshipElement` and `Operation` was exported unevaluated *and* stamped with a Digest presenting it as complete (clause 6.5.10). List authorization paths also shifted after a denial. | | Concealment held at the Method but not in the projection | `AasRegistryModel` | `GetSubmodel` answered `BadNotFound` while an anonymous Browse revealed the node, its identifier, semanticId and digest (clause 6.5.7). | | File handles were unsynchronized and unowned | `AasElementFileManager` | The async node manager holds no lock, so concurrent Calls raced a bare `Dictionary`; sequential handles let any Session read, seek or commit another's. Writes were also unbounded. | | Projection host was unimplementable outside the stack | `AasEnvironmentProjection` | The handle demanded a type with an internal constructor — our own tests used `FormatterServices.GetUninitializedObject` to get round it. Now mirrors WotCon's interface-typed registration. | | Embedded `FileType` omitted 7 of 10 Mandatory members | `AasEnvironmentMaterializer` (V2) | OPC 10000-3 6.4.4 requires them all; without `Size` and `OpenCount` a Client cannot size a read or detect an abandoned handle. | | Round-trip oracle ignored most of the model | `AasRoundTripComparer` | `Blob`, `File`, `ReferenceElement` and `BasicEventElement` had no case arm, so their fields compared equal by falling off the switch; `valueType`, `semanticId`, `entityType` and `Key.type` were never read. The clause 6.4 losslessness claim rested on it. | Known, unchanged: calling an `Operation` with arguments answers `BadTooManyArguments`, because `NodeState.AddChild` does not route through `MethodState.FindChild`. Documented in `docs/Aas.md` §11. Not addressed in this pass, and worth a maintainer decision: OPC 30270 §9.2 says all NodeIds of Nodes not defined in the companion specification **shall not** use the standard namespaces, and Table 87 marks a vendor-specific instance namespace Mandatory. Both halves currently mint instances in the companion namespace. Separating them touches NodeId minting, the runtime and the client together, so it is better done deliberately than folded into a review pass. This is a large change and touches shared code (`Opc.Ua.Types`, the source generator, the xRegistry projection). It is deliberately a draft: the enabling changes are separable if maintainers would prefer them split into their own PRs. The latest `master` is merged into this branch in `0dd0ff0e1`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8 Copilot-Session: c3d88950-4a65-4ff6-87d2-1f545a875c9b Copilot-Session: b8ab3763-b9bd-46ae-b0ef-045db26e5782
690 lines
36 KiB
Markdown
690 lines
36 KiB
Markdown
# OPC UA for Asset Administration Shell (I4AAS)
|
|
|
|
This repository implements two **OPC UA for Asset Administration Shell**
|
|
metamodel generations in one assembly set. They are separate alternatives, not
|
|
revision levels of one model:
|
|
|
|
* **OPC 30270, "OPC UA for Asset Administration Shell"** maps the AAS V2.0.1
|
|
metamodel into namespace `http://opcfoundation.org/UA/I4AAS/` and lives in
|
|
`Opc.Ua.Aas.V2`.
|
|
* **OPC UA for Asset Administration Shell V3 draft** (release `3.00-draft3`)
|
|
maps the AAS V3 draft metamodel into namespace
|
|
`http://opcfoundation.org/UA/I4AAS/v3/` and lives in `Opc.Ua.Aas.V3`.
|
|
|
|
The shared root `Opc.Ua.Aas` namespace contains only code that is genuinely
|
|
metamodel-neutral: deterministic NodeId encoding and `idShortPath` handling,
|
|
`AasOptional<T>`, digest calculation, materialization diagnostics, the AASX
|
|
container walk and the value/operation provider contracts.
|
|
|
|
| Project | Purpose |
|
|
|---------|---------|
|
|
| `Opc.Ua.Aas` | Source-generated I4AAS V2 and V3 models from `src/Opc.Ua.Aas/Design`, the V2 object model and ingestion readers, the V3 object model and read/write serializers, shared identity/value helpers, materialization diagnostics and AASX container support. |
|
|
| `Opc.Ua.Aas.Server` | Server-side projection for both metamodels over `INodeManagerLifecycle`; V2 ingestion materialization and runtime callbacks; V3 registry service, generational materialization, environment export, federation, package integrity and provider contracts. |
|
|
| `Opc.Ua.Aas.Client` | High-level V2 and V3 clients for deterministic metamodel NodeIds, plus the V3 AAS registry client and typed group/resource clients. |
|
|
| `Opc.Ua.Aas.WoT` | The V3 informative Annex F bridge between an AAS environment and a WoT Connectivity Thing Description projection. |
|
|
| `Opc.Ua.Aas.Tests` | NUnit tests for V2 model ingestion/materialization/server/client behavior and V3 model, identity, values, serialization, registry, materialization, packages, federation, DPP and WoT bridge behavior. |
|
|
|
|
The V3 AAS implementation builds on the shared [xRegistry](XRegistry.md)
|
|
registry base and reuses the runtime NodeManager lifecycle described in
|
|
[Runtime NodeSets](RuntimeNodeSets.md). V2 is the OPC 30270 metamodel half only:
|
|
it has no registry, packages, federation, DPP or WoT bridge. The IDTA HTTP API
|
|
described by Annex G of the V3 draft is intentionally out of scope; this
|
|
implementation exposes AAS through OPC UA services and, for V3, the
|
|
xRegistry-compatible registry AddressSpace.
|
|
|
|
---
|
|
|
|
## Choosing V2 or V3
|
|
|
|
Host **one** AAS generation in a server: either V2 or V3. The stack does not
|
|
enforce that rule at startup, because the two NodeManagers can technically be
|
|
registered side by side, but the product decision is that a deployment chooses
|
|
one alternative and exposes one metamodel generation. Running both would publish
|
|
two different AAS metamodels for the same product domain and leave clients to
|
|
guess which one is authoritative.
|
|
|
|
Choose **V2** when the published OPC 30270 specification and the AAS V2.0.1
|
|
metamodel are the interoperability target. The implementation reads V2 JSON, XML
|
|
and AASX documents, materializes them into the `http://opcfoundation.org/UA/I4AAS/`
|
|
AddressSpace, serves Read + Write + Call, and publishes the OPC 30270 Table 83
|
|
conformance units. It does not export the AddressSpace back to AAS documents,
|
|
because OPC 30270 does not require a lossless round trip.
|
|
|
|
Choose **V3** when the draft AAS V3 metamodel and everything built on the draft
|
|
registry half are required: registry, updateable materialization, environment
|
|
export, packages, package integrity, federation, DPP vocabulary and the WoT
|
|
bridge. V3 reads and writes JSON, XML and AASX because its draft requires a
|
|
lossless document/AddressSpace round trip.
|
|
|
|
---
|
|
|
|
## 1. V3 core architecture
|
|
|
|
The V3 draft companion specification has two independent halves:
|
|
|
|
* **Metamodel half** — shells, submodels, concept descriptions and submodel
|
|
elements are materialized as typed OPC UA nodes, following clauses 6.1 and
|
|
6.2. The metamodel surface is Read + Write + Call: value Variables are served
|
|
through an injectable `IAasValueProvider`, and `AASOperationType.Invoke` is
|
|
served through an injectable `IAasOperationHandler`.
|
|
* **Registry half** — a catalogue of shell, submodel, concept-description,
|
|
package and environment documents is served as folders of `FileType` resources
|
|
using the xRegistry model (clause 6.5). The registry answers discovery and
|
|
document-retrieval questions even when the live metamodel tree is hosted
|
|
elsewhere.
|
|
|
|
Where both halves are hosted, the stored documents are canonical and the live
|
|
nodes are derived. A write to a materialized value writes back to the document
|
|
and bumps a version; a refresh derives a new generation from the document. The
|
|
default retirement policy is `AasProjectionRetirementPolicy.Graceful`, so
|
|
existing monitored items can keep reading the old generation until they drain.
|
|
|
|
The V3 half also adds the OPC UA `Decimal` DataType wire encoding needed
|
|
by clause 6.3.1, and the source generator fix needed for recursive structure
|
|
DataTypes.
|
|
|
|
---
|
|
|
|
## 2. Shared identity and V3 value fidelity
|
|
|
|
### Deterministic NodeIds and BrowseNames
|
|
|
|
Clause 6.1.3 assigns deterministic String NodeIds. `AasNodeIdEncoding` is the
|
|
public implementation of the reversible, node-kind-discriminated encoding:
|
|
|
|
```csharp
|
|
string shellId = AasNodeIdEncoding.CreateIdentifiableId(
|
|
AasNodeKind.Shell,
|
|
"https://example.com/aas/42");
|
|
|
|
string elementId = AasNodeIdEncoding.CreateElementId(
|
|
"https://example.com/submodels/nameplate",
|
|
"Nameplate.ManufacturerName");
|
|
|
|
if (AasNodeIdEncoding.TryParse(elementId, out AasParsedNodeId parsed))
|
|
{
|
|
Console.WriteLine(parsed.IdShortPath);
|
|
}
|
|
```
|
|
|
|
`AasIdShortPath` builds and parses the metamodel `idShortPath` convention, and
|
|
`AasBrowseNameAllocator` implements the derived BrowseName rule for
|
|
identifiables that do not carry `idShort`.
|
|
|
|
### xsd value mapping
|
|
|
|
`AasXsdTypeMap` maps every `AASDataTypeDefXsdDataType` value to exactly one OPC
|
|
UA DataType, as required by clauses 6.1.2 and 6.3.1. `AasLexicalCanonicalizer`
|
|
parses and canonicalizes xsd lexical values, and `AasValueSpaceComparer`
|
|
compares values in the XML Schema value space rather than by string equality:
|
|
|
|
```csharp
|
|
ExpandedNodeId dataTypeId = Opc.Ua.Aas.V3.AasXsdTypeMap.ToDataTypeId(
|
|
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal);
|
|
|
|
bool parsed = Opc.Ua.Aas.V3.AasLexicalCanonicalizer.TryParse(
|
|
"1.500000",
|
|
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal,
|
|
out Variant value,
|
|
out string? error);
|
|
|
|
bool equivalent = Opc.Ua.Aas.V3.AasValueSpaceComparer.AreEquivalent(
|
|
"1.500000",
|
|
"1.5",
|
|
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal);
|
|
```
|
|
|
|
The object model uses `AasOptional<T>` so absent remains distinct from
|
|
present-but-empty (clause 6.1.5). Serializers and round-trip comparison preserve
|
|
that distinction.
|
|
|
|
---
|
|
|
|
## 3. V3 reading, writing and materializing AAS documents
|
|
|
|
V3 AAS JSON, XML and AASX are read into the same object model. JSON and XML readers
|
|
return diagnostics instead of throwing for malformed documents:
|
|
|
|
```csharp
|
|
await using FileStream input = File.OpenRead("environment.json");
|
|
Opc.Ua.Aas.V3.AasDocumentReadResult read = await new Opc.Ua.Aas.V3.AasJsonReader()
|
|
.ReadAsync(input, ct);
|
|
|
|
if (!read.Succeeded)
|
|
{
|
|
Console.WriteLine(read.Error);
|
|
return;
|
|
}
|
|
|
|
Opc.Ua.Aas.AasMaterializationResult materialized = Opc.Ua.Aas.V3.AasEnvironmentMaterializer
|
|
.Materialize(read.Environment!);
|
|
|
|
await using FileStream output = File.Create("environment.aasx");
|
|
await new Opc.Ua.Aas.V3.AasxPackageWriter().WriteAsync(output, read.Environment!, ct);
|
|
```
|
|
|
|
The inverse path serializes a clause 6.1.6 NodeSet back to an AAS environment:
|
|
|
|
```csharp
|
|
Opc.Ua.Aas.V3.AasSerializationResult serialized = Opc.Ua.Aas.V3.AasEnvironmentSerializer
|
|
.Serialize(materialized.NodeSet);
|
|
|
|
await using FileStream json = File.Create("roundtrip.json");
|
|
await new Opc.Ua.Aas.V3.AasJsonWriter().WriteAsync(json, serialized.Environment, ct);
|
|
```
|
|
|
|
The round-trip guarantee in clause 6.4 is equivalence, not byte identity. A
|
|
canonical value rewrite such as `"1.500000"` to `"1.5"` is equivalent for
|
|
`xs:decimal`; losing digits, conflating absent with empty or changing ordered
|
|
arrays is not equivalent.
|
|
|
|
---
|
|
|
|
## 4. Hosting the V3 metamodel server
|
|
|
|
`AddAasV3Server` registers the environment NodeManager, default providers and the
|
|
runtime projection host. It must be used with the normal server hosting feature:
|
|
|
|
```csharp
|
|
services.AddSingleton<IAasValueProvider, MyAasValueProvider>();
|
|
|
|
services
|
|
.AddOpcUa()
|
|
.AddServer(options =>
|
|
{
|
|
options.EndpointUrls.Add("opc.tcp://localhost:4840/AasServer");
|
|
})
|
|
.Services
|
|
.AddOpcUa()
|
|
.AddAasV3Server(options =>
|
|
{
|
|
options.EnvironmentFolder = "aas-environments";
|
|
options.RetirementPolicy = AasProjectionRetirementPolicy.Graceful;
|
|
})
|
|
.AddOperationHandler<MyAasOperationHandler>();
|
|
```
|
|
|
|
`EnvironmentFolder` is read by `FolderAasEnvironmentProvider`; when it is not
|
|
set, `InMemoryAasEnvironmentProvider` is used. The metamodel server can also be
|
|
constructed directly with `AasEnvironmentNodeManagerFactory` when an application
|
|
manages `StandardServer.NodeManagerFactories` itself.
|
|
|
|
The runtime uses `IAasValueProvider` for reads and writes of materialized value
|
|
Variables:
|
|
|
|
```csharp
|
|
public sealed class MyAasValueProvider : IAasValueProvider
|
|
{
|
|
public ValueTask<AasValueReadResult> ReadValueAsync(
|
|
NodeId valueNodeId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<AasValueReadResult>(new AasValueReadResult(
|
|
ServiceResult.Good,
|
|
Variant.From("running"),
|
|
StatusCodes.Good,
|
|
DateTime.UtcNow));
|
|
}
|
|
|
|
public ValueTask<ServiceResult> WriteValueAsync(
|
|
NodeId valueNodeId,
|
|
Variant value,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<ServiceResult>(ServiceResult.Good);
|
|
}
|
|
}
|
|
```
|
|
|
|
`IAasOperationHandler.InvokeAsync` receives already-marshalled input and
|
|
in-output values and returns the four outputs required by `AASOperationType.Invoke`.
|
|
A failed AAS operation is represented as a Good OPC UA Call with `Success = false`
|
|
and a diagnostic, which is distinct from a failed Call service result.
|
|
|
|
---
|
|
|
|
## 5. Using the V3 metamodel client
|
|
|
|
`Opc.Ua.Aas.Client.AasClient` computes the deterministic NodeIds of clause 6.1.3, opens the
|
|
generated `*TypeClient` proxies, reads values with their declared xsd type and
|
|
writes lexical values using that type:
|
|
|
|
```csharp
|
|
var client = new Opc.Ua.Aas.Client.AasClient(session, aasNamespaceIndex, telemetry);
|
|
|
|
Opc.Ua.Aas.V3.AASPropertyTypeClient temperature = client.OpenProperty(
|
|
"https://example.com/submodels/process",
|
|
"Temperature");
|
|
|
|
Opc.Ua.Aas.Client.AasValueReadResult current = await client.ReadValueAsync(temperature.ObjectId, ct);
|
|
Console.WriteLine($"{current.ValueType}: {current.LexicalValue}");
|
|
|
|
StatusCode writeStatus = await client.WriteLexicalValueAsync(
|
|
temperature.ObjectId,
|
|
"42.5",
|
|
ct);
|
|
```
|
|
|
|
Operations keep the OPC UA Call status separate from the AAS success flag:
|
|
|
|
```csharp
|
|
Opc.Ua.Aas.V3.AASOperationTypeClient reset = client.OpenOperation(
|
|
"https://example.com/submodels/process",
|
|
"Reset");
|
|
|
|
Opc.Ua.Aas.Client.AasOperationInvokeResult result = await client.InvokeAsync(
|
|
reset.ObjectId,
|
|
ArrayOf<Variant>.Empty,
|
|
ArrayOf<Variant>.Empty,
|
|
0,
|
|
ct);
|
|
|
|
if (StatusCode.IsGood(result.CallStatusCode) && !result.Success)
|
|
{
|
|
Console.WriteLine(result.Diagnostic);
|
|
}
|
|
```
|
|
|
|
Dependency injection registers either a factory over an already connected
|
|
`ManagedSession`, or a lazy factory that asks the registered OPC UA client for a
|
|
session:
|
|
|
|
```csharp
|
|
services
|
|
.AddOpcUa()
|
|
.AddClient(options => { /* endpoint and application options */ })
|
|
.AddAasV3Client(options =>
|
|
{
|
|
options.InstanceNamespaceUri = "urn:example:aas:instances";
|
|
});
|
|
|
|
Func<CancellationToken, Task<Opc.Ua.Aas.Client.AasClient>> clientFactory =
|
|
sp.GetRequiredService<Func<CancellationToken, Task<Opc.Ua.Aas.Client.AasClient>>>();
|
|
Opc.Ua.Aas.Client.AasClient aas = await clientFactory(ct);
|
|
```
|
|
|
|
---
|
|
|
|
## 6. V3 AAS registry
|
|
|
|
The V3 AAS registry is a concrete xRegistry specialization (clause 6.5). It exposes
|
|
the well-known `AASRegistry` object under `Server`, projects shell groups,
|
|
submodel files, concept dictionaries, packages and environment documents, and
|
|
wires the AAS discovery methods:
|
|
|
|
* `LookupShellsByAssetLink(Name, Value)` returns matching shell group NodeIds.
|
|
* `GetSubmodel(SubmodelIdentifier)` returns the document bytes, `Format` and
|
|
`ContentType`, while preserving `Bad_UserAccessDenied` and `Bad_NotFound` as
|
|
meaningful outcomes.
|
|
|
|
The service keeps immutable snapshots. Every mutation advances `Generation`, and
|
|
existing readers keep their previous snapshot:
|
|
|
|
```csharp
|
|
var registry = new Opc.Ua.Aas.Server.Registry.AasRegistryService();
|
|
await registry.UpsertResourceAsync(new Opc.Ua.Aas.Server.Registry.AasUpsertResourceRequest
|
|
{
|
|
GroupSourceIdentity = "https://example.com/aas/42",
|
|
ResourceSourceIdentity = "https://example.com/submodels/nameplate",
|
|
GroupKind = Opc.Ua.Aas.Server.Registry.AasRegistryEntityKind.Shell,
|
|
ResourceKind = Opc.Ua.Aas.Server.Registry.AasRegistryEntityKind.Submodel,
|
|
Content = ByteString.From(File.ReadAllBytes("nameplate.json")),
|
|
ContentType = "application/aas+json",
|
|
Format = "aas/3.0+json"
|
|
});
|
|
|
|
Opc.Ua.Aas.Server.Registry.AasGetSubmodelResult document = await registry.GetSubmodelAsync(
|
|
"https://example.com/submodels/nameplate");
|
|
```
|
|
|
|
To expose the registry in a manually configured server, add the registry node
|
|
manager factory to the server's node manager factories:
|
|
|
|
```csharp
|
|
var registry = new Opc.Ua.Aas.Server.Registry.AasRegistryService();
|
|
server.NodeManagerFactories.Add(
|
|
new Opc.Ua.Aas.Server.Registry.AasRegistryNodeManagerFactory(registry));
|
|
```
|
|
|
|
The V3 AAS registry projection uses the shared `XRegistryProjectionEngine` from
|
|
`Opc.Ua.XRegistry.Server`, so base xRegistry lifecycle, labels and file-transfer
|
|
semantics match the WoT registry implementation.
|
|
|
|
### Registry client
|
|
|
|
`AasRegistryClient` derives from `XRegistryClient`. It resolves the well-known
|
|
root, inherits the base xRegistry lifecycle helpers, and adds the AAS discovery
|
|
methods and typed resource clients:
|
|
|
|
```csharp
|
|
Opc.Ua.Aas.Client.Registry.AasRegistryClient registry =
|
|
await Opc.Ua.Aas.Client.Registry.AasRegistryClient.ForServerAsync(
|
|
session,
|
|
telemetry,
|
|
ct);
|
|
|
|
ArrayOf<NodeId> shells = await registry.LookupShellsByAssetLinkAsync(
|
|
"serial",
|
|
"42",
|
|
ct);
|
|
|
|
Opc.Ua.Aas.Client.Registry.AasGetSubmodelDocumentResult submodel = await registry.GetSubmodelAsync(
|
|
"https://example.com/submodels/nameplate",
|
|
ct);
|
|
|
|
if (StatusCode.IsGood(submodel.StatusCode))
|
|
{
|
|
File.WriteAllBytes("nameplate.json", submodel.Document.ToArray());
|
|
}
|
|
```
|
|
|
|
The DI shortcut is `AddAasV3RegistryClient`:
|
|
|
|
```csharp
|
|
services
|
|
.AddOpcUa()
|
|
.AddClient(options => { /* endpoint and application options */ })
|
|
.AddAasV3RegistryClient();
|
|
```
|
|
|
|
---
|
|
|
|
## 7. V3 updateable registry and environment export
|
|
|
|
`AasMaterializationCoordinator` implements the optional updateable-registry
|
|
profile of clause 6.5.9. It reads `AasMaterializationDocument` entries from an
|
|
`IAasMaterializationDocumentStore`, prepares shadow generations, atomically
|
|
switches them through an `IAasEnvironmentProjectionHost`, records per-document
|
|
outcomes and skips unchanged digests unless `Force` is set.
|
|
|
|
Important behaviours:
|
|
|
|
* **Documents are canonical.** Materialized nodes can be rebuilt from the stored
|
|
documents and the specification. No extra per-node state is required.
|
|
* **Switches are atomic.** Shadow generations are not browsable and do not emit
|
|
model-change events until committed.
|
|
* **Failures retain the previous generation.** Invalid desired versions stay in
|
|
the registry with diagnostics while the previous active generation continues
|
|
serving.
|
|
* **Graceful retirement is the default.** Existing monitored items survive an
|
|
update until retained work drains; `Immediate` invalidates affected monitored
|
|
items with `BadNodeIdUnknown`.
|
|
* **Value write-back bumps versions.** Writes through materialized nodes update
|
|
the stored document and avoid redundant projection when the digest is unchanged.
|
|
|
|
`AasEnvironmentExporter` implements clause 6.5.10. It exports the whole
|
|
materialized environment as AAS JSON, XML or AASX and filters the result per the
|
|
calling session's read access. A filtered export does not publish a digest,
|
|
because the bytes depend on the caller.
|
|
|
|
---
|
|
|
|
## 8. V3 federation, packages, DPP and WoT
|
|
|
|
### Federation
|
|
|
|
AAS federation follows clause 6.5.6. `AasFederationIdentity` derives proxy
|
|
identity from the remote AAS identifier attributes, never from the local endpoint.
|
|
`AasResourceUrlFederationResolver` and `AasOpcUaFederationResolver` apply a
|
|
fail-closed egress policy: host/scheme controls, DNS and connected-address
|
|
checks, redirect/time/size bounds, no ambient credentials and OPC UA peer
|
|
identity validation.
|
|
|
|
### Packages
|
|
|
|
`AasPackageIntegrity` and `IAasPackageStore` implement the package integrity
|
|
rules in clause 6.5.4. Package versions publish immutable `Digest` and
|
|
`DigestAlg`; consumers verify the returned blob by default. OCI-backed packages
|
|
verify the manifest digest, require exactly one package layer descriptor, keep
|
|
mutable tags separate from immutable `VersionId`s and keep referrers out of the
|
|
package version collection.
|
|
|
|
### Digital Product Passport vocabulary
|
|
|
|
The `Dpp/` namespace provides a small Digital Product Passport vocabulary layer.
|
|
`AasDppDisclosurePolicy` maps DPP regulatory classes to the AAS `Public` and
|
|
`Controlled` disclosure tiers, while preserving the finer disclosure class and
|
|
authorization text for consumers.
|
|
|
|
### WoT bridge
|
|
|
|
`Opc.Ua.Aas.WoT` implements the informative Annex F projection. `AasWotBridge`
|
|
reads a bundle of Thing Descriptions carrying the AAS and OPC UA WoT Binding
|
|
terms, resolves the type binding (`@type`, `ua:HasTypeDefinition` or both), and
|
|
projects the object graph described by Annex F. It does not replace the WoT
|
|
Connectivity registry; it is the AAS-specific bridge between the two companion
|
|
specifications.
|
|
|
|
---
|
|
|
|
## 9. Security and disclosure notes
|
|
|
|
Registry writes and file updates inherit the xRegistry security model. The AAS
|
|
`GetSubmodel` convenience method deliberately re-checks authorization on the
|
|
target resource; permission to call the method is never substituted for
|
|
permission to read the file. Denied responses return no bytes, no parse metadata
|
|
and no size or digest. Concealed targets and nonexistent targets follow the same
|
|
externally visible branch.
|
|
|
|
`DisclosureTier` and `Authorization` are metadata, not credentials. The
|
|
implemented DPP policy maps regulatory classes to public or controlled tiers,
|
|
and tests assert that `AASAuthorizationOptionDataType` carries configuration
|
|
only, not passwords, tokens, keys or secrets.
|
|
|
|
Federation resolves untrusted metadata and therefore fails closed. A resolver
|
|
policy, DNS, redirect, credential, certificate, identity or resource-bound
|
|
failure terminates resolution without returning or caching bytes from the
|
|
rejected destination.
|
|
|
|
---
|
|
|
|
## 10. V3 conformance matrix
|
|
|
|
The following table maps the sixteen V3 draft clause 10 conformance units to tests in
|
|
`tests/Opc.Ua.Aas.Tests`. Each cited test was checked against the behaviour it
|
|
asserts; no unit is intentionally left unmapped.
|
|
|
|
A Server publishes the units it actually enables. Both NodeManagers implement
|
|
`IConformanceContributor`, and the server aggregates their contributions into
|
|
`Server/ServerCapabilities/ConformanceUnits`, so the published set follows what
|
|
was wired up rather than what the assemblies could in principle do:
|
|
`AAS-OperationInvoke` appears only when an operation handler other than
|
|
`DefaultAasOperationHandler` is configured, and `AAS-Packages` (with
|
|
`AAS-PackageIntegrity`, which clause 10 requires alongside it) only when a
|
|
package group is present. The unit names are held in `AasConformanceUnits`.
|
|
|
|
`ServerProfileArray` gains no AAS entry. Clause 10 names conformance units but
|
|
assigns no server profile URIs, and the IDTA profile identifier of Annex G is
|
|
qualified on also implementing the HTTP binding, which this stack does not — so
|
|
publishing it would claim conformance that is not met.
|
|
|
|
| Conformance unit | Demonstrating tests |
|
|
|------------------|---------------------|
|
|
| `AAS-Metamodel` | `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsAnnexBMembers`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
|
|
| `AAS-SubmodelElements` | `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsAnnexBMembers`; `Serialization.AasSerializationTests.JsonRoundTripPreservesEverySubmodelElementType` |
|
|
| `AAS-ValueFidelity` | `Values.AasXsdTypeMapTests.EveryDeclaredXsdTypeIsAssignedADataType`; `Values.AasXsdTypeMapTests.NoDataTypeIsAssignedToTwoXsdTypes`; `Values.AasLexicalCanonicalizerTests.CanonicalizationIsIdempotent`; `Client.AasClientTests.ReadAndWriteValueUseDeclaredXsdType` |
|
|
| `AAS-InstanceMaterialization` | `Materialization.AasEnvironmentMaterializerTests.OrderedListUsesHasOrderedComponentAndUnorderedListUsesHasComponent`; `Materialization.AasEnvironmentMaterializerTests.ListMembersAreNamedByIndexAndCarrySequentialIndex`; `Materialization.AasEnvironmentMaterializerTests.MaterializingTheSameEnvironmentTwiceProducesByteIdenticalNodeSet` |
|
|
| `AAS-LosslessRoundTrip` | `Materialization.AasEnvironmentRoundTripTests.MaterializeThenSerializeProducesEquivalentEnvironment`; `Materialization.AasEnvironmentRoundTripTests.SerializeThenMaterializeProducesEquivalentNodeSet`; `Materialization.AasEnvironmentRoundTripTests.ConflatingAbsentWithEmptyIsReported`; `Materialization.AasEnvironmentRoundTripTests.RewritingValueIntoCanonicalLexicalFormIsNotReported` |
|
|
| `AAS-Registry` | `Registry.AasRegistryProjectionTests.ProjectionExposesEverySourceIdentityVerbatim`; `Registry.AasRegistryProjectionTests.ConcreteRegistryMethodsCarryDeclarationIds`; `Registry.AasRegistryServiceTests.UpsertResourceAdvancesGenerationAndPreservesExistingSnapshots` |
|
|
| `AAS-RegistryIdentity` | `Registry.AasRegistryServiceTests.IdentifierConstructionMatchesXRegistryAndIsInvariantAcrossVersions`; `Registry.AasRegistryProjectionTests.ProjectionExposesEverySourceIdentityVerbatim`; `Client.AasRegistryClientTests.TypedGroupAndResourceClientsExposeSourceIdentities` |
|
|
| `AAS-RegistryVersioning` | `Registry.AasRegistryServiceTests.VersionOrderingResolvesNewestVersionNotLaterThanMoment`; `Client.AasRegistryClientTests.VersionsResolveNewestVersionNotLaterThanMoment` |
|
|
| `AAS-Discovery` | `Registry.AasRegistryServiceTests.LookupShellsByAssetLinkFindsShellAndBoundsUnauthenticatedResults`; `Registry.AasRegistryGetSubmodelContractTests.AuthorizedCallerGetsDocumentFormatAndContentType`; `Client.AasRegistryClientTests.LookupShellsByAssetLinkReturnsHitAndMiss`; `Client.AasRegistryClientTests.GetSubmodelReturnsDocumentOnSuccess` |
|
|
| `AAS-OperationInvoke` (partial, see section 11) | `Server.AasRuntimeInvocationTests.InvokeWithCorrectArityReturnsOperationOutputsAsync`; `Server.AasRuntimeInvocationTests.InvokeWithArityMismatchReturnsBadInvalidArgumentAsync`; `Server.AasRuntimeInvocationTests.InvokeOperationFailureReturnsGoodCallStatusAndFalseSuccessAsync`; `Client.AasClientTests.InvokeMarshalsArguments` |
|
|
| `AAS-Federation` | `Federation.AasFederationTests.ProxyIdentityRetainsRemoteAttributesAndIgnoresLocalEndpoint`; `Federation.AasFederationTests.EgressPolicyRejectsRestrictedAddressAndReturnsNoBytes`; `Federation.AasFederationTests.ConnectedAddressRevalidationBlocksDnsRebinding`; `Federation.AasFederationTests.OpcUaPeerIdentityMismatchTerminatesWithoutRemoteRead` |
|
|
| `AAS-DisclosureTiers` | `Registry.AasRegistryServiceTests.DisclosureTierAndAuthorizationAdvertiseConfigurationOnly`; `Registry.AasRegistryServiceTests.ControlledDisclosureTierRequiresAuthenticationButPublicDoesNot`; `Registry.AasRegistryGetSubmodelContractTests.TargetRolePermissionsDenialReturnsUserAccessDenied`; `Dpp.AasDppDisclosurePolicyTests.RegulatoryClassesMapToDisclosureTiers` |
|
|
| `AAS-UpdateableRegistry` | `Updateable.AasUpdateableRegistryMaterializationTests.ShadowGenerationStaysInvisibleUntilAtomicSwitch`; `Updateable.AasUpdateableRegistryMaterializationTests.GracefulRetirementServesExistingMonitoredItemUntilDrain`; `Updateable.AasUpdateableRegistryMaterializationTests.ValidationFailureKeepsPreviousGenerationAndDivergesVersions`; `Updateable.AasUpdateableRegistryMaterializationTests.ValueWriteBackBumpsVersionWithoutRedundantMaterialization` |
|
|
| `AAS-EnvironmentExport` | `Updateable.AasUpdateableRegistryMaterializationTests.EnvironmentExportIsFilteredPerSessionWithNoDigest`; `Serialization.AasxPackageTests.JsonPackageRoundTripsEnvironmentAndSupplementaryFiles`; `Serialization.AasxPackageTests.XmlPackageReadsEnvironment` |
|
|
| `AAS-Packages` | `Packaging.AasPackageIntegrityTests.PublishedVersionExposesImmutableDigestAndDigestAlg`; `Packaging.AasPackageIntegrityTests.MovingTagToNewManifestRetainsDistinctVersions`; `Packaging.AasPackageIntegrityTests.ReferrerDoesNotMutatePackageVersionCollectionOrLifecycle`; `Client.AasRegistryClientTests.PackageDownloadVerifiesDigestByDefault` |
|
|
| `AAS-PackageIntegrity` | `Packaging.AasPackageIntegrityTests.DigestAlgorithmsAcceptOnlyExactAasSpellings`; `Packaging.AasPackageIntegrityTests.MismatchedBlobIsRejectedBeforeItIsReadable`; `Packaging.AasPackageIntegrityTests.ConsumerSideVerificationCatchesTamperedBlob`; `Packaging.AasPackageIntegrityTests.OciBindingPublishesManifestDigestWithPrefixAndDigestWithoutPrefix`; `Packaging.AasPackageIntegrityTests.OciManifestRequiresExactlyOnePackageLayerDescriptor`; `Client.AasRegistryClientTests.PackageDownloadRejectsTamperedBlob` |
|
|
|
|
---
|
|
|
|
## 11. V3 limitations and migration notes
|
|
|
|
* Annex G / IDTA-01002 Part 2 HTTP APIs are not implemented by this branch.
|
|
* **Calling a V3 `Operation` with arguments is incomplete.** A materialized
|
|
Operation carries its `Invoke` Method, and a Client resolves it by either its
|
|
own NodeId or the `MethodDeclarationId` of `AASOperationType.Invoke`. The
|
|
Method also carries `InputArguments` and `OutputArguments` under the standard
|
|
BrowseNames. What does not yet work is the Call itself: `NodeState.AddChild`
|
|
does not route through `MethodState.FindChild`, so a Method imported from a
|
|
NodeSet leaves the typed `InputArguments` property null, and the Server
|
|
answers `BadTooManyArguments`. Closing this needs a change to how the shared
|
|
NodeSet import claims typed children, which affects every consumer of runtime
|
|
NodeSets and is therefore out of scope here. `samples/Aas` exercises the path
|
|
and reports the status rather than working around it. This limitation is V3-specific;
|
|
the V2 `Operation` Method has no declared arguments and is called with an empty
|
|
input list.
|
|
* The AAS registry server currently exposes direct construction through
|
|
`AasRegistryService` and `AasRegistryNodeManagerFactory`; the client has a DI
|
|
helper (`AddAasV3RegistryClient`), while the metamodel server has `AddAasV3Server`.
|
|
* This branch adds new AAS packages and the OPC UA `Decimal` DataType. No manual
|
|
migration from 1.5.378 is required solely to consume the new AAS libraries;
|
|
the general 2.0 migration guidance remains in [Migration Guide](MigrationGuide.md).
|
|
|
|
|
|
---
|
|
|
|
## 12. OPC 30270 / AAS V2 half
|
|
|
|
The OPC 30270 half implements the published "OPC UA for Asset Administration
|
|
Shell" companion specification for the AAS V2.0.1 metamodel. The static model
|
|
is generated into `Opc.Ua.Aas.V2`, its namespace URI is
|
|
`http://opcfoundation.org/UA/I4AAS/`, and predefined nodes are loaded with the
|
|
generated `AddOpcUaAasV2` extension. The server and client entry points are
|
|
`AddAasV2Server` and `AddAasV2Client`.
|
|
|
|
V2 is ingestion-only. `Opc.Ua.Aas.V2.AasJsonReader`,
|
|
`Opc.Ua.Aas.V2.AasXmlReader` and `Opc.Ua.Aas.V2.AasxPackageReader` parse AAS
|
|
V2.0.1 documents and AASX packages into the V2 object model; there is no
|
|
AddressSpace-to-document serializer and therefore no round-trip guarantee. The
|
|
materializer projects the document into an OPC UA NodeSet:
|
|
|
|
```csharp
|
|
await using FileStream input = File.OpenRead("environment.json");
|
|
Opc.Ua.Aas.V2.AasDocumentReadResult read = await new Opc.Ua.Aas.V2.AasJsonReader()
|
|
.ReadAsync(input, ct);
|
|
|
|
if (!read.Succeeded)
|
|
{
|
|
Console.WriteLine(read.Error);
|
|
return;
|
|
}
|
|
|
|
Opc.Ua.Aas.AasMaterializationResult materialized = Opc.Ua.Aas.V2.AasEnvironmentMaterializer
|
|
.Materialize(read.Environment!);
|
|
```
|
|
|
|
`AddAasV2Server` registers the V2 environment NodeManager, the document-backed
|
|
value provider and the shared operation handler contract:
|
|
|
|
```csharp
|
|
services
|
|
.AddOpcUa()
|
|
.AddServer(options =>
|
|
{
|
|
options.EndpointUrls.Add("opc.tcp://localhost:4840/AasV2Server");
|
|
})
|
|
.Services
|
|
.AddOpcUa()
|
|
.AddAasV2Server(options =>
|
|
{
|
|
options.ControlNamespaceUri = "urn:example:aas-v2:instances";
|
|
})
|
|
.AddEnvironmentProvider<MyAasV2EnvironmentProvider>()
|
|
.AddOperationHandler<MyAasOperationHandler>();
|
|
```
|
|
|
|
The runtime surface is Read + Write + Call, matching the rest of this stack. A
|
|
V2 write reaches `IAasValueProvider.WriteValueAsync` and stops there: there is
|
|
no V2 document writer that could persist the change back to JSON, XML or AASX.
|
|
`Operation` is an `OptionalPlaceholder` Method named `Operation` with no
|
|
declared arguments, unlike the V3 mandatory `Invoke` Method, so a V2 Call is
|
|
zero-argument.
|
|
|
|
File and Blob content on V2 is served through an embedded standard OPC UA
|
|
`FileType` object. Clients call `Open`, `Read` and `Close` on that child rather
|
|
than reading a V3-style value projection:
|
|
|
|
```csharp
|
|
var client = new Opc.Ua.Aas.Client.V2.AasClient(session, aasNamespaceIndex, telemetry);
|
|
Opc.Ua.Aas.V2.AASFileTypeClient manual = client.OpenFile(
|
|
"https://example.com/submodels/nameplate",
|
|
"Manual");
|
|
ByteString content = await client.ReadFileContentAsync(manual.ObjectId, ct: ct);
|
|
```
|
|
|
|
OPC 30270 leaves instance NodeIds server-specific and defines no addressing
|
|
convention. The V2 materializer and client deliberately reuse the deterministic
|
|
`AasNodeIdEncoding` and `AasIdShortPath` rules used by the V3 draft, so one
|
|
addressing scheme serves both generations and neither client has to browse for
|
|
known identifiers:
|
|
|
|
```csharp
|
|
var client = new Opc.Ua.Aas.Client.V2.AasClient(session, aasNamespaceIndex, telemetry);
|
|
Opc.Ua.Aas.V2.AASPropertyTypeClient temperature = client.OpenProperty(
|
|
"https://example.com/submodels/process",
|
|
"Temperature");
|
|
|
|
Opc.Ua.Aas.Client.V2.AasValueReadResult current = await client.ReadValueAsync(
|
|
temperature.ObjectId,
|
|
ct);
|
|
|
|
StatusCode status = await client.WriteValueAsync(
|
|
temperature.ObjectId,
|
|
Opc.Ua.Aas.V2.AASValueTypeDataType.Double,
|
|
new Variant(42.5),
|
|
ct);
|
|
```
|
|
|
|
V2 has no registry, no packages, no federation and no DPP. Those are V3 draft
|
|
additions, and the V2 implementation intentionally stops at the OPC 30270
|
|
metamodel.
|
|
|
|
### OPC 30270 conformance units
|
|
|
|
The V2 server publishes the seventeen conformance units from OPC 30270 Table 83
|
|
through `Server/ServerCapabilities/ConformanceUnits` and publishes no
|
|
`ServerProfileArray` entry, because Table 84, which would assign profile URIs,
|
|
is empty. Two internal specification contradictions are resolved in
|
|
`src/Opc.Ua.Aas.Server/V2/AasV2ConformanceUnits.cs`: Table 83 misspells the
|
|
multi-language unit as `I4AAS MultiLangaugeProperty` while Table 85 spells it
|
|
`I4AAS MultiLanguageProperty`, and Table 85 lists an `I4AAS Security` unit that
|
|
Table 83 never defines. The implementation publishes the corrected
|
|
`I4AAS MultiLanguageProperty` spelling and does not publish `I4AAS Security`.
|
|
|
|
Each mapping below was checked against the cited test in
|
|
`tests/Opc.Ua.Aas.Tests/V2/`. No OPC 30270 Table 83 unit is currently unmapped.
|
|
|
|
| OPC 30270 Table 83 unit | Demonstrating tests |
|
|
|-------------------------|---------------------|
|
|
| `I4AAS AAS` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Server.AasV2ServerTests.ConformanceUnitsPublishOpc30270FacetWithoutProfileUris` |
|
|
| `I4AAS Asset` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
|
|
| `I4AAS Submodel` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
|
|
| `I4AAS ConceptDescription` | `Model.AasV2ObjectModelTests.ConceptDescriptionFlavoursRetainIdentifierKinds`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType` |
|
|
| `I4AAS View` | `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Materialization.AasEnvironmentMaterializerTests.ShellMaterializesAssetViewsInterfacesAndAasReferences` |
|
|
| `I4AAS RelationshipElement` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS Property` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Client.AasClientTests.ReadAndWriteValueUseDeclaredAasValueTypeAsync`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS MultiLanguageProperty` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS Range` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS Blob` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers`; `Client.AasClientTests.FileAndBlobContentAreReadThroughEmbeddedFileTypeAsync` |
|
|
| `I4AAS File` | `Serialization.AasV2SerializationTests.AasxReaderReadsXmlJsonAndSupplementaryFiles`; `Server.AasV2ServerTests.EmbeddedFileOpenReadAndCloseServeBlobContentAsync`; `Client.AasClientTests.FileAndBlobContentAreReadThroughEmbeddedFileTypeAsync` |
|
|
| `I4AAS ReferenceElement` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS Capability` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS SubmodelElementCollection` | `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Materialization.AasEnvironmentMaterializerTests.OrderedCollectionUsesHasOrderedComponentAndUnorderedCollectionUsesHasComponent`; `AasV2ModelTests.OrderedCollectionRedeclaresTheSubmodelElementPlaceholder` |
|
|
| `I4AAS Operation` | `Materialization.AasEnvironmentMaterializerTests.MaterializedNodeSetImportsIntoAnAddressSpace`; `Server.AasV2ServerTests.OperationMethodInvokesHandlerAndRejectsArgumentsAsync`; `Client.AasClientTests.InvokeCallsTheEmbeddedOperationMethodAsync` |
|
|
| `I4AAS Event` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
| `I4AAS Entity` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
|
|
|
|
---
|
|
|
|
## 13. References
|
|
|
|
* OPC 30270, "OPC UA for Asset Administration Shell", namespace `http://opcfoundation.org/UA/I4AAS/`.
|
|
* OPC UA for Asset Administration Shell V3 draft, release `3.00-draft3`, namespace `http://opcfoundation.org/UA/I4AAS/v3/`.
|
|
* [xRegistry — abstract registry base model](XRegistry.md).
|
|
* [OPC UA WoT Connectivity](WoTConnectivity.md).
|
|
* [Dependency Injection](DependencyInjection.md).
|
|
* [Runtime NodeSets](RuntimeNodeSets.md).
|
|
|