Files
romanett 9b034f8291 Enable nullable reference types centrally; migrate all unset and annotations-only projects (#4700)
# Description

Nullable reference types are now enabled once, in `common.props`, and
every project in the repository builds warning-clean with them.

**Configuration**
- `<Nullable>enable</Nullable>` lives in `common.props`. All per-project
`<Nullable>` settings are removed: 178 project files (enable,
annotations, and the `disable` in the three Aot test projects).
- 419 per-file `#nullable enable`/`restore` directives and the 4
`#nullable disable` directives are removed.
- Kept on purpose:
- The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type
Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`,
`PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the
project setting in generated code, so the directive is required there.
- `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against
the 1.5.378 packages and does not import the repository props.
- `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now
describe the central setting.

**Migration** (the 43 unset and 13 annotations-only projects, roughly
10k diagnostics)
- **Production code: annotated at the declarations.** Covers
`tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the
LDS sample.
- Members, parameters and returns are nullable where null actually
flows.
- Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`,
`IsMethodTypeDesign`).
- `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take
`params object?[]`. `Template.AddReplacement` takes `string?` (rendering
already skips null replacements).
- `IRegisteredServerStore.RegisterAsync` returns
`Task<RegistrationEntry?>`: it returns null when a registration is
removed.
- Dereferences backed by an invariant (for example, the validated model)
use `!`, so runtime behaviour is unchanged.
- **Behavioural changes in production code** (small, deliberate):
- `NodeSetToModelDesign` throws `InvalidOperationException` when the
file is not a NodeSet. Before, it threw a NullReferenceException on the
next line.
- `ModelDesignValidator` starts its lookup tables empty instead of null.
A redundant `if (m_nodes == null) return;` guard is gone.
- **Test code: minimal annotations.**
  - `?` on values that can be null, `!` where the test asserts non-null.
  - `= null!` for fixture members assigned in SetUp.
- `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the
`IEncodeable` constraint.
- A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`)
folded into the adjacent `IsNull`/`Count` checks.

**Notes for reviewers**
- The diff is large (~1,500 files) but almost entirely mechanical: `?`,
`!` and directive removal. The non-annotation changes are listed above.
- Known follow-up, not done here:
`IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay
non-nullable even though the validator returns null before it loads a
dictionary. Making them nullable would ripple through every generator
call site.

## Related Issues

- Fixes #4439

## 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.
- [ ] 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.

The "no new warnings" box covers net10.0 and net48: full non-incremental
builds of `UA.slnx`, `tools/SourceGeneration.slnx`,
`tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0
warnings and 0 errors under the repository's warnings-as-errors
settings. net9.0 and net8.0 also had no nullable diagnostics, but they
were not rebuilt after the final fixes. Tests have not been run locally
yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:48:13 +02:00
..

IntentEnabledRobot

This sample is a small OPC UA Robot Intent server for one stationary UR5e-style arm. It publishes Server/RobotIntent/Controllers/UR5eIntentController, the controller's frames, tools, locations, axes, outputs, programs, description, safety state and OpenUSD representation.

This sample intentionally retains the UR-style six-axis arm and numerical IK. The BinPickingCell sample injects a separate analytic four-axis palletizer through the shared ISimulatedArmKinematics executor seam; changing the bin-picking robot does not change this standalone sample's model or behavior.

Run

dotnet run --project samples\Robotics\IntentEnabledRobot\IntentEnabledRobot.csproj -- --host localhost --port 62840

Default endpoint: opc.tcp://{host}:{port}/IntentEnabledRobot with port 62840. The default host is localhost so the generated server certificate matches local clients. Stop the server before rebuilding the sample; the running process holds its assemblies open.

For demonstration only, the sample maps anonymous users to the well-known Operator role so the command methods can be exercised without user-management setup. A production server should keep the Robot Intent command methods role-restricted and grant Operator only to authenticated operator identities.

Address space

The standalone Robot Intent node manager creates Server/RobotIntent/Controllers. The sample adds one IntentControllerType named UR5eIntentController with:

  • frames World, Base, MechanicalInterface and GripperTcp linked by HasFrameParent;
  • six revolute axes J1..J6, indices 0..5, with limits of +/-360 degrees except J3 at +/-180 degrees to match the simulated kinematics file;
  • one fitted tool ParallelGripper whose TCP frame is the Tool role frame;
  • locations Bin, Fixture, Inspect and Handoff;
  • outputs GripperOpen and BenchLight;
  • programs Home and PickAndPlace;
  • a robot description with a six-joint kinematic chain, reach radius 0.85 m and payload limit 5 kg.

Capabilities

The server truthfully separates host capabilities from executor capabilities. MissionsSupported, MissionHorizonSupported and MissionBranchingSupported are true because IntentControllerHost implements mission submission, updates, cancellation, horizon release and branching step graphs; the executor receives only the individual released intent steps. TrajectorySupported is true because the simulated executor accepts a complete TrajectoryIntentDataType, follows its timed points and reports trajectory deviation. BlendingSupported and ForceControlSupported are false because the simulation neither blends motions nor regulates contact force.

The generated Robot Intent methods pass the StopMode argument to the executor. The simulation differentiates it as application stop urgency only: QuickStop decelerates with the configured acceleration bound, ProcessStop uses a moderate deceleration, OnPath uses a gentler deceleration along the path, and EndOfCycle / EndOfInstruction finish the current simulated motion segment. These modes do not select or imply IEC 60204-1 stop categories.

OpenUSD mapping

The stage serves Bench.usda as the root layer and arm.usda / gripper.usda as referenced assets. Viewer clients discover picked target prims through OpenUSD live bindings, not a hardcoded table. Each OpenUsdRepresentation is mounted on its represented Object with HasAddIn through the shared CreateRepresentation authoring helper. Browse Server/OpenUSD/Representations; for each representation, browse its live bindings and read the binding with SourceSemanticId = "RobotIntent.Location". Its TargetPrimPath is the USD prim and its SourceNodeId is the corresponding LocationType NodeId.

USD prim (TargetPrimPath) SourceNodeId points to Pose in frame world Quaternion (x, y, z, w)
/World/Targets/Bin Bin (0.41, -0.28, 0.829) (0, 0, 0, 1)
/World/Targets/Fixture Fixture (0.48, 0.26, 0.829), rz 25� (0, 0, 0.2164396, 0.9762960)
/World/Targets/Inspect Inspect (-0.25, 0.30, 0.829), rz -20� (0, 0, -0.1736482, 0.9848078)
/World/Targets/Handoff Handoff (-0.46, -0.26, 0.829), rz 40� (0, 0, 0.3420201, 0.9396926)

The location poses are authored in the World Robot Intent frame and numerically match the USD target coordinates; the arm Base frame is also a child of World, so clients can transform consistently.

Target moves

The four published puck poses are reachable, but a straight Cartesian line between arbitrary pucks can cross a joint-limit boundary in this compact UR5e-style simulation. To make the click-to-target sample deterministic, the safety-aware sample executor handles a submitted LinearMoveIntentDataType as a cell-level target move: it first performs an internal joint move to a short pre-target approach pose, then executes the requested linear move into the published target pose. The externally visible command is still the client's linear intent and the final Result reports the published target pose; the internal joint segment demonstrates why Robot Intent exposes both joint and linear motion semantics.

Safety demonstration

Type commands into the server console:

  • stop trips a simulated protective stop; submitted intents fail with NotPermittedInMode.
  • limit 0.05 enables a safe speed limit; faster Cartesian motions fail with SafetyLimitExceeded.
  • reset returns the simulated safety source to nominal.

The safety variables are read-only reports and there is no command method that clears a stop or changes a safe-motion function.