mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 14:43:08 +08:00
master
14 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1bb4bdca1d |
Add Machine Vision Parts 1 and 2 (#4733)
# Description Implement the released [OPC 40100-1](https://reference.opcfoundation.org/specs/OPC-40100-1) and [OPC 40100-2](https://reference.opcfoundation.org/specs/OPC-40100-2) specifications with generated models, provider-backed runtimes, typed clients, MCP workflows, and DI/Machinery integration. - **Part 1 stays in `Opc.Ua.Vision`** and its client/server packages alongside the distinct Perception model. Provide control/acquisition state machines, jobs and synchronized steps, recipe/product/configuration/result management, file transfers, diagnostics, events, and conditions. - **Part 2 is in `Opc.Ua.MachineVision`, `.Client`, and `.Server`.** Provide asset categories, identification, image-handling aspects, health/maintenance data, topology, and transactional updates. The base Vision assemblies do not reference these optional packages; the Part 2 wire model does not depend on Part 1. - Preserve the release-pinned model bytes, wire namespaces and NodeIds. Perception types are in `Opc.Ua.Vision.Perception`; the AMCM generated namespace remains `Opc.Ua.Vision.MachineVision.AMCM`, and asset client/server extension namespaces remain `Opc.Ua.Vision.Client` and `Opc.Ua.Vision.Server`. - Use `AddOpcUaMachineVision` and `AddOpcUaMachineVisionAMCM` for model loading and factory registration. Each has node-collection, encodeable-factory-builder and node-state-factory-builder overloads. These short entry points delegate to the generated implementations; existing namespace-derived generated names remain available for compatibility. - Support multiple independent named systems/assets under one namespace-owning manager. Reuse injectable providers, file binding, bounded memory stores, optional transactional `IFileSystemProvider` persistence, state machines, telemetry and conformance infrastructure. - Keep one `docs/Vision.md` guide, titled `Machine Vision (OPC 40100-1 and OPC 40100-2)`, and one `Opc.Ua.Mcp.Vision` package. Include deterministic server/client samples. - Expose 17 Machine Vision MCP tools, including capabilities/status, bounded relationships and observation, complete small-file uploads/downloads, management, explicit control, conditions, and chunked transfers. Catalogue counts: 39 Vision-specific, 43 with connection tools, 81 Vision+Robotics, 155 Full. Every Machine Vision wire parameter has a schema description. **Compatibility and limits:** `AddVision()` remains Perception-only unless released parts are selected. Standalone Part 2 uses `AddVision().AddMachineVision()`; Machinery composition uses `AddVisionToMachinery()`. Existing tool defaults and command signatures are preserved. Observation owns and cleans up its subscription: 5 seconds/50 notifications by default, capped at 60 seconds/100 notifications. Complete-file tools are capped at 1 MiB; larger files use chunked transfers. Domain Error values remain distinct from UA service failures. **Versioning:** the seven Vision/MachineVision/MCP packages and their Debug variants follow the repository root version instead of being forced to preview. Vision.OpenUsd and its descendants remain preview-only. Development builds still inherit the root prerelease version. No release version, tag, release branch, publication policy, or feed promotion is changed. **Review actions:** all fourteen review threads have action/evidence replies and are resolved. Changes include the unified guide, Perception namespace, separate Part 2 packages and final naming, multi-system ownership explanation, interrupted-write cleanup, observer failure isolation, complete MCP parameter descriptions, and explicit package inventory. The preview-layout migration topic and the requested ReleaseProcess caveat were removed. The test Publish collector now retries recognized transient failures within one cancellation budget and propagates other exceptions. Commit |
||
|
|
3690341c80 |
DI: wire the PrepareForUpdate and Installation methods per spec and advertise only the facets that are met (#4617)
# Description
**Software update methods** (`fix(di): wire PrepareForUpdate
Abort/Resume and Installation Resume`)
- New internal `SoftwareUpdateTransitions` (one per `SoftwareUpdate`)
checks the current state and moves it under one lock, so concurrent
calls cannot both pass a check.
- `Prepare` (§8.4.8.3) is accepted only from `Idle`. The application's
`OnPrepare` callback gets a token that `Abort` cancels; the token source
is disposed only after both the Prepare call and a racing Abort are done
with it, so a handler still running after Abort can use its token.
- `Abort` (§8.4.8.4) moves `Preparing` to `Idle` (`PreparingToIdle`),
cancels the preparation in flight and reports `Failed` ("Aborted")
through `OnPrepareStateChanged`; the aborted `Prepare` call returns
`Bad_InvalidState`. Outside `Preparing` it returns `Bad_InvalidState`.
- `Resume` (§8.4.8.5) no longer runs the Prepare callback. It moves
`PreparedForUpdate` through `Resuming` to `Idle` and is rejected with
`Bad_InvalidState` outside `PreparedForUpdate` or while the installation
is `Installing`.
- `Installation.Resume` (§8.4.9) moves `Error` to `Idle`
(`ErrorToIdle`),
`Bad_InvalidState` otherwise.
- The dispatcher knows the `Resuming` state and the `ResumingToIdle`
transition, so `CurrentState` / `LastTransition` show their names.
**Advertising** (`fix(di): advertise only the DI facets whose mandatory
units are met`)
- `DI Offline`, `DI SU UpdateStatus`, `DI SU FileSystem Loading`,
`DI SU Installation for File System` and `DI SU Installation for Cached
Loading` are no longer claimed (see the issue for why each is unmet).
- The facets are derived from the advertised units with the mandatory
units of the DI 1.05 profile database, so a facet cannot be listed
without them. Today this leaves Locking and Software Update Base;
DeviceIntegrationHost, FileSystem, Direct and Cached Loading come back
by themselves once the missing pieces are built.
- `docs/DeviceIntegration.md` documents both tables and the missing
pieces.
Tests: `SoftwareUpdateMethodTransitionTests` (9 tests) and
`DiNodeManagerConformanceTests` (5 tests) are new;
`DiConformanceFacetTests`
and `PumpAddressSpaceComplianceTests` expected the old claims and are
updated. All 9 transition tests and 8 of the 10 conformance tests fail
against master; the other two are the unchanged locking test and a guard
that every advertised facet carries its mandatory units (master
over-claims
the units too, so the guard alone holds there).
Behaviour change for users: a plain DI server no longer lists the
DeviceIntegrationHost facet, and Direct/Cached/Package software updates
list only Software Update Base.
Fork CI (full matrix incl. macOS) and CodeQL: green — [CI run
36779552603](https://github.com/biancode/UA-.NETStandard/actions/runs/36779552603),
[CodeQL run
36779556125](https://github.com/biancode/UA-.NETStandard/actions/runs/36779556125).
## Related Issues
- Fixes #4616
## 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.
- [ ] 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.
---------
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
|
||
|
|
c4e0721a29 |
Add a documentation learning path and fix issues found while verifying it (#4555)
## Summary
### Documentation
- Stage the documentation index as a learning path: a first application,
independent client, server, and PubSub paths, then data and services,
companion models, production, advanced integration, and draft or
experimental capabilities.
- Add Getting started (repository samples or NuGet packages), OPC UA
concepts, Your first information model, and a production readiness
checklist.
- Add Native AOT application publishing, a first PubSub publisher and
subscriber exchange, and the UDP transport options.
- Add or expand contents navigation throughout `docs/`, and keep
contributor documentation separate.
- Correct outdated SDK examples and capability guidance, consolidate
repeated explanations into canonical guides, and repair local links and
heading structure.
- Rewrite What's New in 2.0 as a concise feature overview with short
linked details; keep migration instructions in the Migration Guide.
- Expand the feature catalogue with specification IDs, GDS, LDS, the OPC
10000-21 onboarding registrar, ISA-95, xRegistry, OpenUSD, and
sample-local models; distinguish draft status, explain crypto
pluggability, and bring performance guidance forward.
- Address review feedback on wording, active monitored items during
shadow reload, and the `Samples.md` filename and references.
- Regenerate the `opcua-v20-migration` plugin's bundled copy of the
migration guides so it matches the edited `docs/migrate/2.0.x` guides.
- Merge the latest `master`, preserving upstream resource-isolation
guidance within the task-based documentation index.
- Retain deferred Lingity findings where appropriate; this PR does not
claim that every document meets the Lingity score thresholds.
### Fixes found while verifying the guides
Each fix has regression tests that fail without it.
- **Client:** V2 subscriptions compared their lifetime with the
operation timeout instead of the session timeout, and logged a spurious
warning. `ISubscriptionEngineContext`, which is new in 2.0, gains a
`SessionTimeout` property; implementations must add it.
- **PubSub:** a subscriber now registers the `DataSetMetaData`
configured on its readers (OPC 10000-14 6.2.9.4), and the UADP decoder
decodes DataSetMessages that carry no ConfigurationVersion. Before, a
secured subscriber of the reference sample received no DataSets.
- **Source generator:** a ModelDesign method that declares its arguments
inline, without a method type, produced code that did not compile; it
now uses `MethodState`. The generated code of existing models is
unchanged.
- **MinimalClient sample:** waits for its first data-change notification
before it disconnects.
### Known issues, not addressed here
- UDP multicast: the publisher does not send on the interface that the
subscriber joins, and `MulticastLoopback` defaults to false. The PubSub
guide uses unicast for the exchange on one computer.
- The UDP metadata announcement is sent unsecured on secured writer
groups; a secured subscriber drops it with a warning.
- A server does not start in globalization-invariant mode, because
`ResourceManager` creates `CultureInfo("en-US")`. The Native AOT guide
cautions against the mode.
## Validation
- `git diff --check` and
`./.azurepipelines/validate-migration-plugin.ps1` (15 bundled migration
docs validated, plugin manifests synchronized).
- Local Markdown links, anchors, file-link casing, and contents lists
checked across all 87 documents under `docs/`. The index links every
top-level guide and every target of the previous index.
- Both Getting started routes, Your first information model, the PubSub
exchange, and the Native AOT publish of the Getting started client and
server were run as documented. The NuGet route and the tutorial use the
public `2.0.0-preview.5` packages.
- The changed libraries build for all target frameworks in Release
without warnings.
- Tests on .NET 10: Opc.Ua.SourceGeneration.Tests 199 passed;
Opc.Ua.SourceGeneration.Core.Tests 3,977 passed, 8 skipped;
Opc.Ua.Server.Tests `Fluent` category 621 passed; Opc.Ua.Client.Tests
subscription-engine tests 70 passed; Opc.Ua.PubSub.Tests 1,374 passed, 2
skipped; PubSub fuzz replay 145 passed. The new and affected tests also
pass on .NET Framework 4.8.
- Earlier merge validation: 174 resource-isolation tests passed on .NET
10.
- The full solution suite was not run locally.
Review and validation artifacts are maintained outside the repository.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
||
|
|
96c5ef1623 |
Run the fluent configure pipeline in FluentNodeManagerBase (#4537) (#4538)
## Proposed changes Closes #4537. The configure pipeline now lives in `FluentNodeManagerBase`, so a hand-written fluent node manager only overrides `ConfigureAsync` and no longer writes `CreateAddressSpaceAsync` itself. `FluentNodeManagerBase.CreateAddressSpaceAsync` now: 1. loads the predefined nodes (as `AsyncCustomNodeManager` did before), 2. creates a builder for `NamespaceIndex` with `CreateFluentBuilder`, 3. awaits `ConfigureAsync(builder, ct)`, 4. runs `RegisterAuthoredNodesAsync` and then `CompleteConfigureAsync`, 5. finishes with `SealConfigurationAsync`, which seals the builder, replays `NotifyNodeAdded`, then activates behaviors and simulations. ### Breaking change (2.0) Some managers need a different order, for example a post-setup runner before sealing, a builder for another namespace, or work after sealing. They must stop calling `base.CreateAddressSpaceAsync`, because that would now run a second, empty pipeline and seal the manager-owned registries too early. These managers now call `LoadPredefinedNodesAsync(SystemContext, externalReferences, ct)` and keep driving their own pipeline: - the source-generated `NodeManager` template (the ordering test in `NodeManagerGeneratorTests` is updated) - `DiNodeManager`, `Isa95NodeManager`, `PositioningNodeManager`, `RobotIntentNodeManager`, `VisionNodeManager` - `samples/OpenUsd/SiteCompositionServer/SiteNodeManager` These now use the base pipeline: - `samples/FluentApi/BasicFluentApiServer/BasicFluentNodeManager` becomes a single `ConfigureAsync` override. Before this change it never sealed its builder. - the hosting `FluentNodeManager` (from `AddNodeManager(namespaceUri, build)`) becomes a `ConfigureAsync` override. It now seals through `SealConfigurationAsync`, so `OnNodeAdded` handlers wired in the build delegate fire. Before, it called `builder.SealAsync()` directly and they did not. Docs (`NodeManagers.md`, `DeviceIntegration.md`) are updated. `NodeManagers.md` gets a short hand-written `ConfigureAsync` example. ## Types of changes - [x] Breaking change (fix or feature that would cause existing functionality to change) - [x] Enhancement (non-breaking change which adds functionality) ## Checklist - [x] I have read the CONTRIBUTING doc. - [x] I have signed the CLA. - [x] I ran tests locally with my changes, all passed. - [x] I fixed all failing tests in the CI pipelines. - [x] I fixed all introduced issues with CodeQL and LGTM. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added necessary documentation (if appropriate). ## Tests New `FluentNodeManagerBasePipelineTests`: nodes authored in `ConfigureAsync` get registered, the Objects-folder reference is mirrored into `externalReferences`, `OnNodeAdded` fires once, and the builder is sealed afterwards. Local run on net10.0, all passing: - Opc.Ua.Server.Tests (Fluent/Hosting/StateMachine/RuntimeNodeSet filter): 1286 - Opc.Ua.SourceGeneration.Core.Tests (NodeManager filter): 40 - Opc.Ua.Di.Tests 426, ISA95 136, Positioning 69, Robotics 636, Robotics.Intent 22, Vision 484 (+1 skipped), Vision.Intent 14, OpenUsd 921 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
668310f21f |
Make the fluent node manager's configure/seal path genuinely async (#4442)
# Description The fluent node-manager pipeline had two synchronous chokepoints, and two independent efforts have now hit both of them. `Configure` is a `partial void`, so it cannot `await`. A manager whose wiring depends on asynchronous setup had to invent a second hook outside the pipeline, create its own builder there, and hand-roll `CreateFluentBuilder(...).Configure(...).Seal()` inside it. `Seal()` was synchronous, so nothing a registration staged during `Configure` could be completed by awaiting. Registrations that needed to await either blocked — `EventSourceRegistry` did `.GetAwaiter().GetResult()` on the manager's root-notifier registration — or could not be expressed at all. ## What changed **`FluentNodeManagerBase.ConfigureAsync(INodeManagerBuilder, CancellationToken)`** is the awaitable wiring seam. It runs once per activation, with the manager's attached builder, immediately *before* the synchronous `Configure` partial(s) — so instances it materialises are in the address space by the time `Configure` wires callbacks against them. The default implementation is a no-op. It is a `virtual` method rather than a second `partial` declaration because a partial method can be optional (`partial void`, no return value) or awaitable (an extended partial method, which must be implemented) — not both, and the generator would otherwise have to detect which one the user declared. `partial void Configure(INodeManagerBuilder)` keeps working unchanged; a manager uses either hook or both. **`NodeManagerBuilder.Seal()` becomes `SealAsync(CancellationToken)`.** Sealing is now the single point where registrations that could not finish inside `Configure` get their turn to await. Every seal site awaits it, so a later change can move registry *setup* onto the same seam. **`DiNodeManager` now runs the standard pipeline itself** — create and attach the builder, `await ConfigureAsync`, `RegisterAuthoredNodesAsync`, `CompleteConfigureAsync`, the post-setup runner, then `await builder.SealAsync` — instead of handing subclasses a bare callback and leaving them to build and seal on their own. DI subclasses gain node registration and the reverse-reference pass, which they previously skipped. Sealing deliberately comes **after** the post-setup runner. The fluent registries are owned by the manager, not by a single builder, and sealing starts them — so sealing first locks post-setup configurators out of registering simulation loops of their own, which is exactly what `AddRobotics`' configurators do. Sealing before the runner makes `MinimalRobotServer` fail to start with *"Cannot add a simulation loop after the registry has started"*. Worth knowing for follow-up work: with several builders attached to one manager, the first seal freezes the shared registry for all of them. ## Workarounds removed - **`DiNodeManager.OnAddressSpaceReadyAsync`** — deleted. Its three overriders (`RoboticsNodeManager`, and the `PumpNodeManager` / `GeneratorNodeManager` samples) now override `ConfigureAsync` and no longer hand-roll `CreateFluentBuilder(...).Configure(Configure).Seal()`. - **`EventSourceRegistry`'s `.GetAwaiter().GetResult()`** on `AddRootNotifierAsync` — gone. `Publish(..., RegisterAsRootNotifier: true)` now stages the registration and `SealAsync` drains it, so the wait happens on an awaited path. A source whose root-notifier registration fails is rolled out of the registry before the failure surfaces, and a registry disposed before its builder sealed drops what it staged. - **`Isa95NodeManager.ConfigureStatusEvents()`** — the synchronous second builder pass is now `ConfigureStatusEventsAsync(CancellationToken)`, awaited from `CreateAddressSpaceAsync`. (`ConfigureCatalogChanges()` stays synchronous: it starts a background task and never seals a builder, so there was nothing to convert.) - **`IRoboticsBuildContext.Seal()`** — now `SealAsync(CancellationToken)`. These really were `NodeManagerBuilder.Seal()` calls behind a same-named method. The state transition still happens under the context lock; the builder's asynchronous completion runs outside it, so no `await` is taken while the lock is held. - The Pump / Generator incremental registration callbacks (`onRegistered`) changed from `Action<T>` to `Func<T, CancellationToken, ValueTask>` so their per-instance builder pass can await its own seal. No sync-over-async was introduced anywhere. `Dispose(bool)` is unchanged and stays non-blocking; teardown remains in `DeleteAddressSpaceAsync`. `NodeManagerBuilder.Seal()` and `IRoboticsBuildContext.Seal()` are removed rather than kept as synchronous overloads. A synchronous seal that silently skipped the asynchronous completion would reintroduce exactly the failure mode this change exists to remove. ## Generator `NodeManagerTemplates` emits, in order: predefined nodes load → `await ConfigureAsync(__m_builder, cancellationToken)` → `Configure(__m_builder)` and the typed `Configure` → `RegisterAuthoredNodesAsync` → `CompleteConfigureAsync` → `await SealConfigurationAsync(__m_builder, cancellationToken)`. `NodeManagerGeneratorTests` asserts that order literally, and that neither `__m_builder.Seal()` nor the synchronous `SealConfiguration(__m_builder)` is emitted. ### Merging with #4432 #4432 landed on `master` while this was in review and reworked the same seam from the other side: it split `Seal()` into `SealGraphAuthoring()` + `StartSimulations()` so a manager can replay `NotifyNodeAdded` *between* the two halves — sealing first stops a lifecycle handler authoring nodes nothing would register, starting the simulations last stops a simulated value change preceding its own node''s `OnNodeAdded` — and added `FluentNodeManagerBase.SealConfiguration(builder)` as the one call the generator emits. Both intents are kept. That split is precisely why the asynchronous completion could not simply live inside `SealAsync`: a manager reaching the seam through the split path would silently skip it, which is the failure mode this PR exists to remove. So the asynchronous half became its own step: - `NodeManagerBuilder.CompleteSealAsync(ct)` drains the staged root-notifier registrations, then starts the simulations. `SealAsync(ct)` is `SealGraphAuthoring()` followed by it. - #4432''s synchronous `StartSimulations()` is gone, folded into `CompleteSealAsync`. It had exactly one production caller after the merge, and leaving it standing would have kept a second, synchronous way to activate a builder that skips the staged registrations — the very hazard this PR removes. Activation is now one step with one entry point; the *split* itself stays, because the replay has to sit between the halves. - `SealConfiguration` becomes `SealConfigurationAsync(builder, ct)`: seal the graph, replay `NotifyNodeAdded`, then `CompleteSealAsync`. Activation still happens last, so #4432''s ordering guarantee holds unchanged. ## Consumers unblocked - **#4434 ("Reimplement the GDS ApplicationsNodeManager as a generated fluent node manager")** adds `OnAddressSpaceReadyAsync` to `FluentNodeManagerBase` purely because, in its own words, *"`Configure` is a `partial void` and cannot await"*. That hook is unnecessary once `ConfigureAsync` exists — the GDS manager overrides `ConfigureAsync` instead, and the ordering it wants (predefined nodes → async setup → `Configure`) is what the generator now emits. #4434 has not merged yet, so it can drop its addition rather than have it simplified afterwards. - **Branch `romanett/fluent-node-behaviors`** moved `SimulationRegistry`, `EventSourceRegistry`, `MonitoredSourceRegistry` and the alarm wiring onto `AttachToType`/`Attach` for *release* only, because activation ran from `CompleteConfigureAsync` and most managers only called the synchronous `Seal()`. Every seal site now reaches `SealAsync`, so registry and alarm *setup* can move onto behavior activation too — the remaining half of that work. ## Tests - New `DiNodeManagerConfigureAsyncTests` drives the real `DiNodeManager.CreateAddressSpaceAsync` and asserts the hook runs once with an attached builder, that nodes it stages are registered, that their references to externally owned nodes are published, and that the builder is sealed before `CreateAddressSpaceAsync` returns. - `PublishTests` now pins the root-notifier deferral contract: registration is staged during `Configure`, lands only on the seal, and draining twice does not double-register. - Every `Seal()` call site in the test suites became `await ...SealAsync()`, including the three #4432 added in `NodeSetImportBuilderTests` and `SimulationBuilderExtensionsTests`. ## Related Issues No tracking issue was opened for this; it is the shared prerequisite extracted from #4434 and branch `romanett/fluent-node-behaviors`, both of which had independently worked around the same synchrony. Happy to open one if maintainers want the ADR trail. ## 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. - [ ] 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. ### What was actually run locally `UA.slnx` builds clean. Post-merge on **net10.0**: `Opc.Ua.Server.Tests` 5020/0 and `Opc.Ua.SourceGeneration.Core.Tests` 3813/0. Pre-merge the same run plus `Opc.Ua.Di.Tests` 425/0, `Opc.Ua.Robotics.Tests` 627/0, `Opc.Ua.ISA95.Tests` 136/0, `Opc.Ua.Positioning.Tests` and `Opc.Ua.Robotics.Intent.Tests` were all green; `MinimalRobotServer` was also started directly to confirm the hosted robotics path comes up. Three caveats, so the unchecked box above is not a mystery: the full suite was **not** run against .NET Framework locally; `Opc.Ua.OpenUsd.Tests` has one pre-existing failure on this machine (`StructuredCartesianCoordinatesAreAccepted` expects `"1.5"` and gets `"1,5"` — a German-locale decimal separator, unrelated to this change); and one pre-existing `CA1861` warning comes from `NodeSetImportIntegrationTests.cs`, which is byte-identical to `master` on this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
df44b460be |
Complete CreateInstanceOf node lifecycle during registration (#4360)
# Description Generated `CreateInstanceOf<Type>` factories build typed node graphs and assign per-instance NodeIds, but intentionally leave the node create lifecycle open. Hand-written callers commonly registered those graphs without calling `CreateAsPredefinedNode`, so `OnBeforeCreate`, `OnAfterCreate`, and change-mask clearing never ran. This change: - adds `NodeState.IsCreated` and makes lifecycle completion idempotent per node; - completes newly materialized children, including children created by `OnAfterCreate`; - bounds late-child lifecycle convergence and fails explicitly if callbacks keep adding uncreated nodes; - completes uncreated nodes automatically before predefined-node registration and behavior wiring in both async and legacy custom node managers; - assigns/rebases async-node-manager NodeIds before lifecycle callbacks; - emits a null-safe Debug diagnostic when registration performs completion; - documents the generated-factory lifecycle contract and migration behavior; - adds regressions for condition/alarm method binding, finite-state-machine namespaces, Fluent state-node rebasing, behavior replacement, cloning, deletion, late children, non-converging callbacks, and synchronous/async registration. The tracked `src/` and `samples/` call-site audit covered 2,953 C# files. No additional ordering-sensitive call sites require explicit completion beyond the three existing DI/Robotics sites. ## Validation - `dotnet build UA.slnx -c Release -p:CustomTestTarget=net10.0`: passed. - `Opc.Ua.Server.Tests` net10.0: 4,811 passed, 5 skipped. - `NodeStateTests` net10.0 and net48: 213 passed on each. - Node-manager lifecycle and null-logger tests net48: 459 passed. - Source-generation core tests net10.0 and net48: 3,802 passed, 8 skipped on each. - DI, Robotics, ISA95, Positioning, Vision, Vision.Intent, and XRegistry suites passed on net10.0 and net48. - Generated alarm lifecycle regression passed on net10.0 and net48. The solution build reports six pre-existing CA warnings in untouched files. The OpenUSD suite has one pre-existing locale-sensitive failure in `StructuredCartesianCoordinatesAreAccepted` (`1.5` expected versus `1,5` under `en-DE`); all other OpenUSD tests passed on both tested frameworks. ## Related Issues - Fixes #4320 ## Checklist - [ ] 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. - [ ] 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. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7ffdc400-0710-4a2e-a9d1-c0e1a9b3b81e |
||
|
|
d525bc3f28 |
Bound reconnect cleanup and organize samples by domain (#4276)
# Description This PR fixes bounded ManagedSession reconnect cleanup and reorganizes the sample applications into domain-focused folders. ## Reconnect cleanup - Bounds reconnect integration cleanup with independent 30-second close and dispose limits. - Sends `CloseSession` through the awaited transport request path during `Session.DisposeAsync`. - Preserves explicit `Session.CloseAsync` behavior that waits for or cancels outstanding publish requests. - Covers the disposal request path with a regression test. ## Sample organization - `samples/OpenUsd`: `GeneratorServer`, `SiteCompositionServer` - `samples/Redundancy`: `RedundantClient`, `RedundantServer`, `RedundantPubSub` - `samples/Isa95`: `MinimalIsa95Server` - `samples/MinimalApi`: `MinimalBoilerServer`, `MinimalCalcServer`, `MinimalClient` - `samples/DI`: `PumpDeviceIntegrationServer` - `samples/Lds`: `ConsoleLdsServer` - `samples/Reference`: `ConsoleReferenceClient`, `ConsoleReferenceServer` - `samples/PubSub`: `ConsoleReferencePubSubClient` `UA.slnx` now exposes the same requested domain groups. Project references, tests, runtime sample probes, CI workflows, Dockerfiles, Compose files, helper scripts, and documentation were updated to the new paths. `samples/Robotics/MinimalRobotServer` remains under `Robotics`. ## Validation - `dotnet build UA.slnx` - 3 Redundancy short-haul sample tests - 70 DI sample tests - 914 OpenUSD tests - 1 WotCon model-materialization test - 137 AOT tests - `docker compose config` for all 6 moved Redundancy Compose files ## Related Issues - Fixes #4273 ## Checklist - [ ] 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 built the complete `UA.slnx` solution locally. - [ ] I ran all tests locally against at least .NET Framework and .NET 10. - [ ] 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: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <mschier@microsoft.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Copilot-Session: a2c2838f-74ae-4997-a1d4-50574d9e0636 |
||
|
|
a6a5275006 |
Add a PumpX-2000 datasheet and diagrams to the pump sample (#4138)
# Description The `PumpDeviceIntegrationServer` sample simulates OPC 40223 pumps but had no product documentation, a thin nameplate, and a duty point that was not physically self-consistent. This PR gives the sample an **official-style product datasheet** for its simulated asset and aligns the server to it, so the document and the running address space cannot drift apart. ### New: `samples/PumpDeviceIntegrationServer/DATASHEET.md` A vendor-style datasheet for the fictitious *SimPump Corp PumpX-2000* (clearly marked as a simulated device): - Nameplate table mapped to OPC UA browse paths **and namespaces** (DI / Machinery / Pumps) - Best-efficiency duty point, characteristic curves and a performance table - Operating limits, motor / materials / dimensions data - Instrument list (tag → browse path → unit → `EURange`) and a Mermaid process schematic - Alarm trip points and the deterministic simulation profile ### Server aligned to the datasheet - `PumpDatasheet.cs` — the datasheet expressed as compile-time constants. - `MaterialiseNameplate` — materialises the 19 optional identification properties through the generator-emitted `AddXxx(context)` helpers, so each carries the browse name, namespace and DataType declared by the model (21 nameplate fields in total per pump). - `WithIdentification` now assigns the full datasheet nameplate to **every** simulated unit. Product-level fields are shared; unit-level fields (`SerialNumber`, `AssetId`, `ComponentName`, `Location`, `FabricationNumber`) are derived from the pump number, so `--pumps N` yields a consistent nameplate for each instance. - **Simulation rebuilt around the datasheet curves.** Volumetric flow is the only independent variable; differential pressure, mass flow, efficiency and shaft power are derived from `H(Q) = 32 − 0.0104·Q²`, `η(Q) = 72·(1 − 0.6·((Q−25)/25)²)` and `P = ρ·g·Q·H/η`, so `P = Δp·Q/η` holds at every tick. Previously every measurement was an independent sine and the sample published 5 kW of shaft power for 0.05 kg/s at 2 bar Δp. - `EURange` values now come from the datasheet; `OverTempAlarm` reports the `BearingTemperature` chain (the correct `SourceNode` for an alarm whose limits are in Kelvin) rather than a boolean; `Cavitation` and `MotorOverheat` are derived from suction level and bearing temperature with hysteresis instead of `tick % n`. ### Diagrams The sample README gains a simulated-device summary, a link to the datasheet, and three Mermaid diagrams: **address space**, **startup / hosting sequence**, and **simulation + alarm dataflow**. ### Drift protection `PumpDatasheetConformanceTests` pins the nameplate, engineering ranges, trip points, published-value envelope and hydraulic consistency against the datasheet, and the exact pump node-surface baseline in `PumpInstanceNodeIdRegressionTests` covers the new identification properties. No changes to `src/` — this is sample, test and documentation only. ### Rebased on #4117 master has been merged into this branch. #4117 reshaped the same sample (loop-based pump creation via `--pumps N`, `Pump_1` / `Pump_2` BrowseNames with `Pump #n` DisplayNames, historian wiring, `WireBoolean`, per-pump `Diagnostics` group). Both intents are preserved: - the datasheet nameplate is applied through upstream's per-pump `WithIdentification(builder, pump, pumpNumber)` hook, so it scales to `--pumps N`; - `Program.cs` is left exactly as upstream has it — identification is configured centrally, so the old per-pump block there is gone; - the datasheet curve values are published through upstream's status-code / source-timestamp plumbing, and upstream's `WireBoolean` (TrueState/FalseState text + history) is kept; - the node-surface baseline is the union of both identification lists; - `docs/SourceGeneratedNodeManagers.md` was consolidated upstream into `docs/NodeManagers.md`, so the snippet fixes were ported there. ## Related Issues _No tracking issue; this is a documentation and sample-quality improvement to the pump reference sample._ ## 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 (post-merge) - `dotnet build samples/PumpDeviceIntegrationServer` (net10.0): 0 warnings, 0 errors - `dotnet build tests/Opc.Ua.Di.Tests` (net10.0): 0 warnings, 0 errors - `dotnet test tests/Opc.Ua.Di.Tests -f net10.0`: **331/331 passed** (includes upstream's new `PumpAddressSpaceComplianceTests` and `DiConformanceFacetTests`) - `dotnet test tests/Opc.Ua.Di.Tests -f net48 --filter Pump`: **44/44 passed** - Live client read against the merged sample: Δp 214 079 Pa, ṁ 8.654 kg/s, η 69.33 %, P 2 677.4 W — `Δp·(ṁ/ρ)/η` matches the published shaft power; `Pump_1` reads SN-001 / PMP-1001 / Feed Pump A / Bay 3 and `Pump_2` reads SN-002 / PMP-1002 / Feed Pump B / Bay 4, exactly as documented. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe608993-f3c9-4779-a6b9-9a9eabfc24ba |
||
|
|
8a10139b8c |
Fix OPC UA address-space compliance in the device integration stack (#4117)
# Description
A live OPC UA compliance audit of `samples/PumpDeviceIntegrationServer`
against the **OPC 40223 Pumps** and **OPC 10000-100 (DI)** companion
specifications found **13 address-space violations** plus a
serialization bug in the MCP tool. Every fix lands at its **root cause
in the shared stack** rather than as a workaround in the sample, so all
servers benefit.
The sample now exposes **N fully simulated pumps** (`--pumps N`) and
advertises the DI conformance facets it actually satisfies.
> This branch has been merged with `master` including **#4116** ("Add
simulation to PumpDeviceIntegrationServer and Event modelling"), which
independently reworked the same pump simulation and the same fluent
alarm builders. See [Relationship to #4116](#relationship-to-4116) below
for how the two were reconciled.
## Findings and fixes
| # | Finding | Root cause | Fix |
|---|---------|-----------|-----|
| 1 | `OverTempAlarm` created by the fluent API did not exist in the
address space | `AttachAlarm` attached the alarm with `AddChild` but
never indexed it into the node manager | Register the node; the same gap
existed in `AddObject`, `CreateInstance` and the state-machine creators
|
| 2 | Alarms were unsubscribable — `EventNotifier = 0` everywhere, no
`HasNotifier` from `Server` | No event-source wiring at all | Set
`HasCondition`, initialise
`SourceNode`/`SourceName`/`ConditionName`/`InputNode`, promote
`EventNotifier` up the ancestor chain, publish `HasNotifier` from the
`Server` object |
| 3 | Every instance NodeId was minted in the **DI standard namespace**
(`ns=4;s=5001_Pump #1`) | `DiNodeManager.New` inherited the parent's
namespace index | New `DiNodeManager.InstanceNamespaceIndex`; instances
move to the server's application namespace |
| 4 | `Server/Namespaces` described only 2 of 6 namespaces | The
NodeSet2 importer drops nodes parented to namespace 0, so companion-spec
metadata objects never reached the address space — and no route covered
the server's own namespace | New `NamespaceMetadataPublisher` walks the
`NamespaceArray` and fills version/publication date from
`ModelDependencyAttribute` |
| 5 | Vendor BrowseNames (`Diagnostics`, `LastError`, …) sat in the DI
namespace | Builders defaulted to the parent's namespace | Default to
the server namespace; **spec-defined BrowseNames are unchanged** |
| 6 | `AccessLevel="5"` from the Pumps NodeSet was reported as `1` | The
ModelDesign `AccessLevel` enum is not `[Flags]` and cannot represent
`CurrentRead \| HistoryRead` | Carry the verbatim NodeSet2 bitmask and
emit named `AccessLevels` constants |
| 7 | `Historizing = true` while `HistoryRead` returned
`BadHistoryOperationUnsupported` | Nothing reconciled the declared
history surface with the wired historians | Startup clears the
advertisement (including masking the attribute read callbacks) when no
provider resolves; the sample wires a real historian |
| 8 | Mandatory `ProductInstanceUri` unset | Sample omission | Populated
on every pump |
| 9 | Mandatory `TrueState`/`FalseState` empty on every supervision
boolean | Absent in the official NodeSet; a server must still populate
them | Populated on every pump |
| 10 | Machinery `Machines` folder empty (OPC 40001-1 §9.2 says
**shall**) | `TryAddToMachinesFolder` used `AddChild`, which
*re-parents* the device away from `DeviceSet` | Add the `Organizes`
reference instead |
| 11 | BrowseName contained a space and `#` | Sample naming | `Pump_1`
with the readable label in `DisplayName` |
| 12 | `Maintenance` group materialised but empty | Sample omission |
Populated |
| 13 | Uninitialised values reported `Good` | No initial-value status |
`BadWaitingForInitialData` until the first simulation tick |
| — | `ServerProfileArray` advertised only `StandardUA2017` |
`DiNodeManager` did not implement the existing `IConformanceContributor`
| Contribute DI 1.05 server facets computed at runtime from what is
actually wired, merged with `StandardUA2017` |
| — | MCP tool serialised boolean `false` and numeric `0` as JSON `null`
| `Variant.Null` is `default`, so `Variant.Equals` adopts the non-null
type and compares against the default value | Dispatch on `BuiltInType`
and use typed `TryGetValue` accessors instead of the prohibited
`AsBoxedObject` |
## Relationship to #4116
#4116 landed on `master` while this branch was in flight and targeted
overlapping ground. The merge is a **union of both intents**, not a
fast-forward:
- **`AlarmBuilderExtensions`** — kept both sides. #4116 contributes the
parent-must-be-an-Object guard, `SetEnableState`, and the
`HasEventSource` references; this branch contributes the `HasCondition`
reference, condition source initialisation, **registration of the alarm
with the node manager so it is browsable**, notifier promotion up the
whole ancestor chain, and root-notifier registration.
- **`SupervisionBuilderExtensions`** — took #4116's `SetAlarmActive`. It
is more spec-correct than this branch's version: it honours
`EnabledState`, resets Acked/Confirmed on activation, and computes
`Retain` per OPC 10000-9 so an unacknowledged Condition stays retained
after going inactive. This branch's null guard was kept so a malformed
alarm cannot dereference a missing `EnabledState`.
- **`PumpNodeManager.Configure.cs`** — took #4116's **push-based**
simulation (`RegisterPumpSimulation`, `CreatePumpSimulation`,
`IValueUpdater<T>`, `Initialize`/`Advance`/`Publish`, deterministic
per-pump phase offsets) as the structure, then folded in this branch's
compliance work: configurable pump count, Maintenance group,
`TrueState`/`FalseState`, historian wiring, per-pump mandatory
Identification values, and `BadWaitingForInitialData` until the first
tick.
- **README** — union, keeping #4116's address-space validation workflow
section.
Two test adjustments were needed, both because the merge legitimately
changed behaviour the tests pinned:
- `PumpHostedReferenceTests` now resolves pump NodeIds from the browse
result instead of hard-coding the DI namespace, and waits for the first
published value. `PumpCreatedAfterStartupJoinsTheLiveSimulationAsync`
was **added** to restore #4116's coverage that a pump created after
startup through `ConfigureDevicesFor` joins the live simulation.
- `FluentAlarmRegistrationIntegrationTests` no longer asserts that
`Retain` clears when an alarm goes inactive — that pinned this branch's
weaker semantics, and #4116's Part 9 behaviour is correct.
## Breaking change
Runtime-created DI device instance NodeIds move from the companion-spec
namespace to the server's application namespace, e.g.
`ns=<DI>;s=5001_Pump #1` becomes `ns=<application>;s=5001_Pump #1`. This
invalidates NodeIds cached by clients from earlier 2.0 previews. It is
**not** a model change — standard DI/Machinery/Pumps type NodeIds and
spec-defined BrowseNames are untouched.
Device integration hosting is new in 2.0, so there is nothing to migrate
from 1.5.378 and this is deliberately *not* in `docs/MigrationGuide.md`.
Anyone tracking 2.0 previews should rediscover devices by browsing
`DeviceSet` rather than persisting NodeIds, and resolve namespace
indexes from `NamespaceArray` per connection.
## Verification
Compliance is pinned by regression tests that assert **through the
server's service surface** (`BrowseAsync` / `ReadAsync` /
`HistoryReadAsync`), not by inspecting `NodeState` objects in memory.
That distinction mattered: three defects survived in-memory assertions
and were only caught through the read path — most subtly, `Historizing`
was cleared while `AccessLevel` still advertised `HistoryRead`, because
attribute read callbacks re-applied the NodeSet bits.
Post-merge on net10.0:
| Suite | Result |
|---|---|
| `Opc.Ua.Di.Tests` | 326 passed |
| `Opc.Ua.Server.Tests` | 3846 passed, 5 skipped |
| `Opc.Ua.Tools.Tests` | 288 passed |
| `Opc.Ua.History.Tests` | 506 passed, 27 skipped |
| `Opc.Ua.SourceGeneration.Core.Tests` | 3722 passed, 8 skipped |
The full solution was also built and tested on **net48** before the
merge.
Review feedback from `copilot-pull-request-reviewer` has been addressed
in commit `badad31dc`: the shared `ServerObjectState` lock was removed
(the forward `HasNotifier` edge is published through the owning node
manager instead), MCP array serialization now preserves element types,
and the pump option guards report the specific rejected option. The only
failure is
`ConfigureApplicationBuildsSharedClientAndServerConfigurationAsync`, a
pre-existing certificate-store issue that **passes when run in
isolation** and is unrelated to this change.
## Documentation
Updated `docs/DeviceIntegration.md`, `docs/MigrationGuide.md`,
`docs/HistoricalAccess.md`, `docs/DeveloperGuide.md`,
`docs/McpServer.md`, `docs/README.md`,
`samples/PumpDeviceIntegrationServer/README.md` and
`tools/Opc.Ua.Mcp/README.md`, and consolidated the four node-manager
documents into a single [`docs/NodeManagers.md`](docs/NodeManagers.md) —
overview, built-in managers (including `MasterNodeManager`), core vs
custom, registration, server address-space metadata, and source
generation — with a TOC and all inbound links repointed.
## Related Issues
No tracking issue exists for this work yet — it originated from an
ad-hoc compliance audit of the pump sample. Given the size of the change
and the runtime-breaking NodeId move, please open (or link) a tracking
issue so the design is recorded as an ADR before merging.
- Fixes #
## 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.
- [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.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0d50c3fc-58b1-4c99-85a4-1e32c88666d9
|
||
|
|
cfd15f08e1 |
Assign instance NodeIds in generated CreateOrReplace<Child> helpers (#4104)
# Description
The model source generator emits two families of instance helpers with
inconsistent NodeId behaviour:
* `ISystemContext.CreateInstanceOf<Type>(parent, browseName)` rebases
the whole materialised subtree onto per-instance NodeIds.
* `<Owner>.CreateOrReplace<Child>(context, replacement)` — the plumbing
behind `NodeState.CreateChild` / `ReplaceChild` — did not. The child it
produced was either bare (null NodeId, no browse name) or kept the
type-level NodeId of the replacement.
Any node manager mixing the two had to hand-roll a recursive re-stamp
before registration, or sibling instances collided on NodeIds — a
failure that only surfaces at registration time.
`samples/PumpDeviceIntegrationServer` did exactly this.
## What changed
**Generator** (`tools/Opc.Ua.SourceGeneration.Core`)
* `CreateOrReplace<Child>` gains `bool assignInstanceNodeIds = true`.
When enabled and the context supplies an `ISystemContext.NodeIdFactory`,
a child whose NodeId is null or still equal to its declaration NodeId —
and its descendants — are rebased through
`NodeInstanceExtensions.AssignInstanceNodeId` /
`AssignInstanceChildNodeIds`, with the owner as reference root. **A
NodeId the caller already assigned is never overwritten.**
* The helper also stamps the new child's `SymbolicName` / `BrowseName` /
`DisplayName`. Without an identity, a path-based `INodeIdFactory`
derives `{parent}_` for *every* child of a parent, so the assignment
would have introduced collisions rather than removed them. `BrowseName`
stamping is guarded on `context.NamespaceUris != null` — event-reporting
paths pass a context without a namespace table.
* All generated `Create*` factory call sites pass
`assignInstanceNodeIds: false`: they build declaration subtrees whose
NodeIds must stay at their type-level values, and the enclosing
`CreateInstanceOf<Type>` factory rebases the finished subtree in a
single pass.
* The `CreateInstanceOf<Type>` rebase guard drops the `parent != null`
requirement — supplying a `browseName` is what marks a dynamically
materialised instance. Calling it without one (as the generated
`NodeStateActivator`s do, and when replacing a well-known singleton)
still keeps the declaration NodeIds.
* `replacement` is now emitted as `BaseInstanceState?` on
`CreateOrReplace<Child>` and on the generated `FindChild` override,
matching the documented contract and the base `NodeState.FindChild`
signature.
**Hand-written twins** —
`MethodState.CreateOrReplace{Input,Output}Arguments` and
`BaseDataVariableState.CreateOrReplaceEnumStrings` gain the same
parameter and behaviour, sharing a new internal
`NodeInstanceExtensions.AssignNewChildInstanceNodeIds`. This is also
required so the generated `public new` overrides keep hiding a matching
base signature (otherwise `CS0109` with `TreatWarningsAsErrors`).
**Sample** — `PumpDeviceIntegrationServer` now passes the real
`DeviceSet` parent to the factory and drops `AssignChildNodeIds` plus
the manual root stamp.
**Docs** — new *"Materialising instances at runtime — NodeId
assignment"* section in `SourceGeneratedNodeManagers.md` and a refreshed
`DeviceIntegration.md`.
## Design note: why not the guard proposed in the issue
Gating on *"the owner already has an instance NodeId"* cannot be
implemented as `owner.NodeId != <ownerTypeNodeId>`. The generated child
factories call `<Owner>.CreateOrReplace<Child>` on **declaration**
instances — `CreatePumpType_Operational` runs on a
`FunctionalGroupState` whose NodeId is `ObjectIds.PumpType_Operational`,
not `ObjectTypeIds.FunctionalGroupType` — so the comparison would be
true while the *type template* is being materialised (`forInstance:
false`) and would rewrite the address space's declaration NodeIds. The
explicit opt-out separates the two callers unambiguously.
## Compatibility
Adding an optional parameter is source-compatible. Behaviour changes for
callers of `NodeState.CreateChild` / `ReplaceChild` when the context
carries a `NodeIdFactory`: the new child now carries a browse name and a
per-instance NodeId instead of nothing.
## Related Issues
- Fixes #4099
> [!NOTE]
> The issue asks to delete `AssignChildNodeIds` from
`samples/MinimalRobotServer`. That sample does not exist on `master` —
it only lives on the still-open #4096 branch and carries no such helper.
The equivalent boilerplate on `master` is
`PumpDeviceIntegrationServer/PumpNodeManager`, which this PR cleans up.
Details in [this
comment](https://github.com/OPCFoundation/UA-.NETStandard/issues/4099#issuecomment-5102275507).
> [!NOTE]
> One commit is an unrelated one-line-scope build-hygiene fix: two
`CA2000` warnings in `ConsoleReferenceClient` where subscription
ownership transfers to the session. Happy to split it out if preferred.
## 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.
- [x] I have addressed **all** PR feedback received.
### Tests added
**Generator emission** (`NodeManagerGeneratorTests`) —
`CreateOrReplaceChildAssignsInstanceNodeIdsByDefault`,
`TypeFactoriesOptOutOfCreateOrReplaceNodeIdAssignment`,
`CreateInstanceOfFactoriesRebaseWithoutAnExplicitParent`.
**Helper behaviour** (`NodeInstanceExtensionsTests`) —
`CreateOrReplaceArgumentsAssignsInstanceNodeIds`,
`CreateOrReplaceArgumentsKeepsCallerAssignedNodeId`,
`CreateOrReplaceArgumentsHonoursTheAssignmentOptOut`,
`CreateOrReplaceEnumStringsAssignsInstanceNodeIds`.
**Pump DI server regression** (`PumpInstanceNodeIdRegressionTests`,
added on review feedback):
| Test | Asserts |
| --- | --- |
| `ConfiguredPumpExposesTheFullModelSurface` | The exact set of **77
model identifiers** a configured pump materialises. |
| `EveryPumpInstanceExposesTheSameGeneratedModelSurface` | A second pump
exposes the identical generated surface, minus what the fluent
`Configure` pass adds. |
| `MaterialisedPumpNodeIdsComeFromTheNodeIdFactory` | Every NodeId is
`{parentIdentifier}_{symbolicName}` in the parent's namespace — minted
by `PumpNodeManager.New`. |
| `MaterialisedPumpNodeIdsAreUniqueAcrossInstances` | No NodeId is null
or shared between two instances of the type. |
| `GeneratedFactoryAssignsInstanceNodeIdsBeforeRegistration` | The
factory output already carries per-instance NodeIds before
`AddPredefinedNodeAsync` applies its defensive rebase. |
| `AlarmSubtreeKeepsStandardDeclarationNodeIds` | Pins the alarm
subtree's standard-namespace ids — a pre-existing gap outside the
generated helpers. |
The 77-identifier baseline was captured by running the same subtree walk
against the pre-change commit and diffing it against the post-change
run: **identical** — same node set, same NodeIds.
### Local test runs
| Suite | net10.0 | net48 | net472 |
| --- | --- | --- | --- |
| `Opc.Ua.SourceGeneration.Core.Tests` | 3722 passed | 3721 passed | — |
| `Opc.Ua.SourceGeneration.Tests` | 81 passed | 81 passed | — |
| `Opc.Ua.SourceGeneration.Stack.Tests` | 90 passed | — | — |
| `Opc.Ua.Types.Tests` | 8083 passed | 8076 passed | — |
| `Opc.Ua.Di.Tests` | 303 passed | 303 passed | 277 passed |
| `Opc.Ua.Core.Tests` | 4134 passed | — | — |
| `Opc.Ua.Gds.Tests` | 1095 passed | — | — |
| `Opc.Ua.History.Tests` | 506 passed | — | — |
| `Opc.Ua.Server.Tests` | 3626 passed, 1 failed | 3574 passed, 1 failed
| — |
The single `Opc.Ua.Server.Tests` failure —
`ServerFluentApiHostingTests.ConfigureApplicationBuildsSharedClientAndServerConfigurationAsync`
— is pre-existing and unrelated; it reproduces on a stashed clean
`master` tree.
> [!NOTE]
> The source-generation test harness compiles generated model code
against a hand-written `Opc.Ua` API stub
(`tests/Opc.Ua.SourceGeneration.Core.Tests/CompilerUtils.cs`, shared by
three test projects). Its 73 `CreateOrReplace<Child>` declarations were
updated to the new signature — without that, every generated call site
passing `assignInstanceNodeIds: false` failed to bind.
`NodesetMethodArgumentTests` pinned the exact emitted call and was
updated to the new one for the same reason as the
`NodeStateGeneratorTests` expectations.
> [!NOTE]
> `master` has been merged into this branch. The only conflict was in
`NodeStateGeneratorTests.cs`, where master rewrapped the expected
call-site strings while this branch appended `assignInstanceNodeIds:
false` to them — kept master's wrapping and re-applied the argument.
Master's two new templates (`Create_MethodArguments`,
`Assign_MethodArgumentValues`) call
`CreateOrReplace{Input,Output}Arguments` while building a declaration
subtree, so they now pass `assignInstanceNodeIds: false` to preserve
their stated intent of leaving the property node's identity untouched.
> [!TIP]
> Changing a source generator leaves dependent projects' cached
generated output stale. If a local build reports `CS1739: ... does not
have a parameter named 'assignInstanceNodeIds'` or `CS0109` on a
generated `CreateOrReplace*`, run `dotnet build <project> -t:Rebuild`.
Clean CI builds are unaffected.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
|
||
|
|
6a92fbd1a1 |
Link EuRange correctly from PumpDeviceIntegrationServer (#4075)
This pull request improves the registration of property nodes created via the fluent API, ensuring that properties such as `EngineeringUnits` and `EURange` are always properly registered with the node manager. It introduces a utility class to centralize node registration and updates both the implementation and tests to reflect this behavior. **Fluent API improvements:** * Refactored `EnsureEngineeringUnitsProperty` and `EnsureEURangeProperty` to always register created properties using the new `FluentNodeRegistration.RegisterCreatedNode` method, ensuring consistent node manager registration. [[1]](diffhunk://#diff-4128c152ec415df1746b49a2e1df0e18cb05e5381c3e5884c6724a0e2c2f1b61L92-R92) [[2]](diffhunk://#diff-4128c152ec415df1746b49a2e1df0e18cb05e5381c3e5884c6724a0e2c2f1b61L132-R132) [[3]](diffhunk://#diff-4128c152ec415df1746b49a2e1df0e18cb05e5381c3e5884c6724a0e2c2f1b61L169-R191) * Updated property creation in `PropertyInitBuilderExtensions.cs` to use the centralized registration utility. **New utility for node registration:** * Added the `FluentNodeRegistration` class to encapsulate logic for registering newly created nodes with an `AsyncCustomNodeManager`. **Testing improvements:** * Added new tests to verify that `EngineeringUnits` and `EURange` properties are both browsable and readable, and that they are properly registered with the node manager. [[1]](diffhunk://#diff-84c2ea5e8e977638cf52c62a81a90c62f67b2d78efd70db62b3ef39c2f86adc4R136-R198) [[2]](diffhunk://#diff-84c2ea5e8e977638cf52c62a81a90c62f67b2d78efd70db62b3ef39c2f86adc4R281-R331) [[3]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27R88-R157) * Refactored and renamed test files and classes for clarity, updating test harnesses and node manager mocks to support the new registration logic. [[1]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27L32-R33) [[2]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27L42-R50) [[3]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27L118-R187) [[4]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27R197-R248) [[5]](diffhunk://#diff-8ff5510d21a0f088a52b9f7be699cc08a4033e9514d2fb491bdfaa55e2d50b27R263-R268)# 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._ ## Related Issues _Reference all GitHub issues this PR addresses. If there is no issue yet, open one and link it here._ _If this is a relatively large or complex change, a design must have been discussed in the related tracking issue and signed off (which becomes the Architectural Decision Record (ADR))._ - Fixes #github-issue-number, ... ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [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. - [ ] 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. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <marcschier@hotmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
0de25de89a |
Change PumpDeviceIntegrationServer to use PumpType (#4066)
This pull request updates the Device Integration documentation and the `PumpDeviceIntegrationServer` sample to introduce and demonstrate the new `ITopologyElementBuilder<TElement>` API for working with OPC UA Device Integration topology elements. The changes clarify the distinction between device and topology-element builders, update sample code to use the new builder, and improve the overall clarity and accuracy of both the documentation and the sample implementation. **API and Documentation Updates:** * Introduced `ITopologyElementBuilder<TElement>` as the unified builder for topology elements, exposing common operations such as functional groups and topology references, and updated all references and examples to use this builder where appropriate. [[1]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaR164-R173) [[2]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaR219-R228) [[3]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaL897-R926) [[4]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaL912-R943) [[5]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaL926-R958) * Clarified the distinction between device builders (`IDeviceBuilder<TDevice>`) and topology-element builders in both the documentation and sample code, and documented the available builder methods and their intended use. [[1]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaL33-R38) [[2]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaL141-R146) [[3]](diffhunk://#diff-93e580c17580d8cb3c5c4206fdf6b64c1deabc1e00e4f83d64583661a97f76eaR495-R500) **Sample Code Modernization:** * Refactored `samples/PumpDeviceIntegrationServer/Program.cs` to use the new `ITopologyElementBuilder<TElement>` API for creating and configuring the second pump, including setting identification properties and functional groups, and removed legacy code for device health and software update facets. [[1]](diffhunk://#diff-7e065631de4a55cb923ba4fa9a32fffcff804ce5ef3e09a70a2eead76425b18dL74-R98) [[2]](diffhunk://#diff-7e065631de4a55cb923ba4fa9a32fffcff804ce5ef3e09a70a2eead76425b18dL133-L139) [[3]](diffhunk://#diff-7e065631de4a55cb923ba4fa9a32fffcff804ce5ef3e09a70a2eead76425b18dL53-L57) [[4]](diffhunk://#diff-7e065631de4a55cb923ba4fa9a32fffcff804ce5ef3e09a70a2eead76425b18dL37-R37) * Cleaned up and modernized the sample project description and removed unused or outdated fields and comments. [[1]](diffhunk://#diff-e3976eed0bf7d7b96d6762d58e8101a0670b2ef60aa04609e8d5ff4be30f64beL8-R8) [[2]](diffhunk://#diff-70693dd3c39308e8e25d4e009281950f860a914afcf92b2e100a0710b5bb8db1L58-L76) [[3]](diffhunk://#diff-70693dd3c39308e8e25d4e009281950f860a914afcf92b2e100a0710b5bb8db1L110-R91) [[4]](diffhunk://#diff-70693dd3c39308e8e25d4e009281950f860a914afcf92b2e100a0710b5bb8db1L129-R110) **Software Update Example Improvements:** * Updated the software update walkthrough in the documentation to use the new builder APIs and improved the clarity of the example, including more accurate references to the simulated device and software update facet. [[1]](diffhunk://#diff-5342d39e79e59ca4fc77a9d6277377841c321d17476979f612c0487c53342238L300-R303) [[2]](diffhunk://#diff-5342d39e79e59ca4fc77a9d6277377841c321d17476979f612c0487c53342238L313-R337) [[3]](diffhunk://#diff-5342d39e79e59ca4fc77a9d6277377841c321d17476979f612c0487c53342238L378-L379) These changes make it easier to work with OPC UA Device Integration topology elements, provide clearer guidance in the documentation, and ensure the sample code reflects current best practices.# 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._ ## Related Issues _Reference all GitHub issues this PR addresses. If there is no issue yet, open one and link it here._ _If this is a relatively large or complex change, a design must have been discussed in the related tracking issue and signed off (which becomes the Architectural Decision Record (ADR))._ - Fixes #github-issue-number, ... ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [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. - [ ] 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. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <marcschier@hotmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
8538f531e7 | Update paths for the consolidated repository layout | ||
|
|
7cec2d614d | Move repository files to the consolidated layout |