# 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>
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,MechanicalInterfaceandGripperTcplinked byHasFrameParent; - six revolute axes
J1..J6, indices0..5, with limits of +/-360 degrees except J3 at +/-180 degrees to match the simulated kinematics file; - one fitted tool
ParallelGripperwhose TCP frame is theToolrole frame; - locations
Bin,Fixture,InspectandHandoff; - outputs
GripperOpenandBenchLight; - programs
HomeandPickAndPlace; - 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:
stoptrips a simulated protective stop; submitted intents fail withNotPermittedInMode.limit 0.05enables a safe speed limit; faster Cartesian motions fail withSafetyLimitExceeded.resetreturns 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.