Positioning: OPC 10000-210 RSL and 10000-211 GPOS on one geolocation provider - #4125
marcschier merged 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds the OPC UA Positioning stack (RSL 10000-210 + GPOS 10000-211) and migrates ISA-95 GeoSpatialLocation onto the shared IGeoLocationProvider seam so one provider can back both structured (GPOS) and textual (ISA-95) representations.
Changes:
- Introduces new
Opc.Ua.Positioningbase library plus...Serverand...Clientpackages, including transforms, hosting, and typed client operations. - Migrates ISA-95 GeoSpatialLocation binding and tests from the ISA-95-specific provider to the shared
IGeoLocationProvider(+ formatter projection to String[] literals). - Adds comprehensive new test project for positioning and updates docs/solution metadata to include the new packages.
Reviewed changes
Copilot reviewed 69 out of 70 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/Opc.Ua.Positioning.Tests/Transforms/Wgs84TransformTests.cs | Adds transform golden/round-trip tests for WGS84 ECEF/ENU conversions. |
| tests/Opc.Ua.Positioning.Tests/Transforms/TransformTestHelpers.cs | Shared helpers for transform tests. |
| tests/Opc.Ua.Positioning.Tests/Transforms/RslFrameTransformTests.cs | Adds extensive tests for RSL frame conventions, composition, inversion, and gimbal lock. |
| tests/Opc.Ua.Positioning.Tests/Transforms/InternalMathTests.cs | Adds tests for internal matrix/vector helper types. |
| tests/Opc.Ua.Positioning.Tests/PositioningServerFixture.cs | Test fixture for hosting a positioning node manager in-process. |
| tests/Opc.Ua.Positioning.Tests/PositioningHostingTests.cs | Tests DI/hosting registration for positioning server/client. |
| tests/Opc.Ua.Positioning.Tests/PositioningAddressSpaceBuilderTests.cs | Tests address-space builder creates RSL/GPOS nodes and validates inputs. |
| tests/Opc.Ua.Positioning.Tests/Opc.Ua.Positioning.Tests.csproj | New test project for positioning. |
| tests/Opc.Ua.Positioning.Tests/ModelGenerationTests.cs | Validates generated namespaces/NodeIds and loader/factory behavior. |
| tests/Opc.Ua.Positioning.Tests/ModelEncodingTests.cs | Validates binary/JSON encoding round-trips for GPOS types. |
| tests/Opc.Ua.ISA95.Tests/Integration/Isa95EndToEndTests.cs | Updates end-to-end ISA-95 test to use shared provider and String[] literal value. |
| tests/Opc.Ua.ISA95.Tests/Common/Isa95GeoSpatialLocationTests.cs | Reworks ISA-95 location tests for shared provider, sourceId, and String[] literals. |
| src/Opc.Ua.Types/Utils/ValueGuards.cs | Adds cross-target guard helpers used by transforms. |
| src/Opc.Ua.Positioning/Transforms/Wgs84CoordinateReferenceSystemTransformer.cs | Adds built-in EPSG:4326 CRS transformer for geodetic <-> ECEF conversion. |
| src/Opc.Ua.Positioning/Transforms/Vector3.cs | Adds internal immutable vector used by transforms. |
| src/Opc.Ua.Positioning/Transforms/SmallLinearAlgebra.cs | Adds small-matrix linear algebra (SVD, inversion) used for fitting. |
| src/Opc.Ua.Positioning/Transforms/RslFrameTransform.cs | Implements RSL Annex B frame transform (compose/invert/convert). |
| src/Opc.Ua.Positioning/Transforms/Matrix3x3.cs | Adds internal immutable matrix used by transforms. |
| src/Opc.Ua.Positioning/Transforms/LocalTangentPlane.cs | Implements ENU tangent plane projections for a CRS. |
| src/Opc.Ua.Positioning/Transforms/ICoordinateReferenceSystemTransformer.cs | Defines CRS transformation extension point. |
| src/Opc.Ua.Positioning/Transforms/GroundControlPointFitResult.cs | Exposes fitted zone transform + diagnostics and mapping helpers. |
| src/Opc.Ua.Positioning/Transforms/GroundControlPointFitOptions.cs | Adds options for fitting, including CRS selection. |
| src/Opc.Ua.Positioning/Transforms/GroundControlPointFitMode.cs | Defines fit modes (rigid/similarity/affine). |
| src/Opc.Ua.Positioning/Transforms/GeographicCoordinates.cs | Helpers for elevation encoding-mask semantics. |
| src/Opc.Ua.Positioning/Transforms/AngleUnit.cs | Adds explicit radians/degrees angle unit enum. |
| src/Opc.Ua.Positioning/Transforms/AngleMath.cs | Adds angle conversion helpers used by transforms. |
| src/Opc.Ua.Positioning/Properties/AssemblyInfo.cs | Assembly metadata tweak for positioning library. |
| src/Opc.Ua.Positioning/Opc.Ua.Positioning.csproj | New positioning model+transform project with source generation inputs. |
| src/Opc.Ua.Positioning/NugetREADME.md | Package README for positioning base library. |
| src/Opc.Ua.Positioning/Model/Opc.Ua.RSL.NodeSet2.csv | Adds RSL NodeId mapping inputs for generator. |
| src/Opc.Ua.Positioning/Model/Opc.Ua.GPOS.NodeSet2.csv | Adds GPOS NodeId mapping inputs for generator. |
| src/Opc.Ua.Positioning.Server/Providers/IRelativeSpatialLocationProvider.cs | Adds relative provider contract + sample type. |
| src/Opc.Ua.Positioning.Server/Properties/AssemblyInfo.cs | Assembly metadata tweak for positioning server. |
| src/Opc.Ua.Positioning.Server/PositioningProviderSubscription.cs | Adds disposable owner for provider watch loops. |
| src/Opc.Ua.Positioning.Server/PositioningNodeManagerFactory.cs | Adds node manager factory advertising both namespaces. |
| src/Opc.Ua.Positioning.Server/PositioningNodeManager.cs | Implements standalone node manager loading RSL+GPOS and metadata ensure. |
| src/Opc.Ua.Positioning.Server/Opc.Ua.Positioning.Server.csproj | New positioning server package project. |
| src/Opc.Ua.Positioning.Server/NugetREADME.md | Package README for positioning server. |
| src/Opc.Ua.Positioning.Server/Hosting/PositioningPostSetup.cs | Adds post-setup runner/context + namespace metadata attachment. |
| src/Opc.Ua.Positioning.Server/Hosting/OpcUaPositioningServerBuilderExtensions.cs | Adds DI hosting extensions for standalone/composed server integration. |
| src/Opc.Ua.Positioning.Server/Hosting/IPositioningServerBuilder.cs | Defines fluent registration surface for providers. |
| src/Opc.Ua.Positioning.Server/EventIds.cs | Adds event IDs for positioning server logging. |
| src/Opc.Ua.Positioning.Client/Properties/AssemblyInfo.cs | Assembly metadata tweak for positioning client. |
| src/Opc.Ua.Positioning.Client/PositioningEntries.cs | Adds typed client DTOs/records for discovered objects and values. |
| src/Opc.Ua.Positioning.Client/PositioningClientOperations.cs | Adds internal client helpers for browse/translate/read. |
| src/Opc.Ua.Positioning.Client/Opc.Ua.Positioning.Client.csproj | New positioning client package project. |
| src/Opc.Ua.Positioning.Client/NugetREADME.md | Package README for positioning client. |
| src/Opc.Ua.Positioning.Client/Hosting/OpcUaPositioningClientBuilderExtensions.cs | Adds DI registration for managed-session client factories. |
| src/Opc.Ua.Positioning.Client/GlobalPositioningClient.cs | Implements high-level GPOS client with reads and streaming observers. |
| src/Opc.Ua.ISA95.Server/Providers/Isa95GeoSpatialLocationServiceCollectionExtensions.cs | Removes ISA-95-specific DI provider registration (replaced by shared seam). |
| src/Opc.Ua.ISA95.Server/Providers/Isa95GeoSpatialLocationProvider.cs | Removes ISA-95-specific in-memory provider (replaced by shared in-memory provider). |
| src/Opc.Ua.ISA95.Server/Providers/IIsa95GeoSpatialLocationProvider.cs | Removes ISA-95-specific provider interface/types (replaced by IGeoLocationProvider). |
| src/Opc.Ua.ISA95.Server/Builders/Isa95ModelBuilder.cs | Switches GeoSpatialLocation creation/binding to shared provider, sourceId, formatter. |
| src/Opc.Ua.ISA95.Server/Builders/Isa95GeoSpatialLocationBinder.cs | Reworks binder to project shared samples into ISA-95 String[] literals w/ push support. |
| src/Opc.Ua.ISA95.Server/Builders/IIsa95ModelBuilder.cs | Updates interface to shared provider + sourceId + formatter parameters. |
| samples/MinimalIsa95Server/README.md | Updates sample docs to shared provider + WKT projection. |
| samples/MinimalIsa95Server/Program.cs | Updates sample to seed shared provider and pass sourceId to builder. |
| docs/Positioning.md | Adds developer documentation for packages, server hosting, providers, clients, transforms. |
| docs/ISA95.md | Updates ISA-95 docs to shared seam, String[] semantics, and new example. |
| UA.slnx | Includes new positioning projects and test project in solution. |
| .gitattributes | Marks NodeSet CSV/XML as binary to preserve byte-for-byte artifacts. |
Comments suppressed due to low confidence (4)
src/Opc.Ua.ISA95.Server/Builders/Isa95GeoSpatialLocationBinder.cs:1
GeoSpatialLocationTypeis documented (and tested) asValueRank=OneOrMoreDimensions, so writingVariant.Nullreintroduces a type-shape mismatch (null variant vs.String[]). Prefer returning an explicitly typed empty string array (e.g.,new Variant(ArrayOf<string>.Empty)or anArrayOf<string>withCount==0) when there are no literals, and useStatusCodeto convey “no data” if needed.
/* ========================================================================
src/Opc.Ua.Positioning/Transforms/GroundControlPointFitResult.cs:1
Span<double> source = [ ... ](and the similartarget = [ ... ]below) may allocate a temporary array depending on compiler/language version and target frameworks. Since this is hot-path numeric code, usestackalloc(or direct locals) for these fixed-size buffers to ensure allocation-free execution across targets.
src/Opc.Ua.Positioning/Transforms/LocalTangentPlane.cs:1Originstores the caller-providedS3DGeographicCoordinateDataTypereference directly. Since these UA data types are mutable, external mutation after construction would makeOrigininconsistent with the already-computedm_originEcef/rotation matrices. Consider copying/cloningorigininto a new instance forOrigin(or documenting that the input must not be mutated after construction).
src/Opc.Ua.Positioning/Transforms/SmallLinearAlgebra.cs:1- The implementation assumes
nis 2 or 3, but will throwIndexOutOfRangeException(or silently compute nonsense) for other sizes, despite being a public utility within the assembly. Add explicit dimension validation (including checkinga.GetLength(0) == a.GetLength(1)andn == 2 || n == 3) and throwArgumentExceptionwith a clear message to make failures deterministic and easier to diagnose.
…thing Review feedback on #4125. ArrayOf<T> implements INullable, so `default` is the null array, which is a different thing on the wire from an empty one. Browsing for children that do not exist means ''no children'', not ''value not present'', so BrowseChildrenAsync now returns ArrayOf<ReferenceDescription>.Empty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
… provider Third in the #4096 stack, on top of the source generator change. Adds the Relative Spatial Location and Global Positioning companion models with their server and client libraries, and migrates ISA-95 onto the IGeoLocationProvider seam introduced by the core stack change at the bottom of this stack. A deployment writes one provider and both OPC 10000-211 and OPC 10030 are served from it: the specifications disagree on representation, not on content, so the difference is handled as a projection. - GPOS publishes the structured GlobalLocationDataType and validates the EPSG code against the configured CRS. - ISA-95 publishes the same sample as the String array that OPC 10030 section 8.3.2.2 requires. The previous binder wrote a scalar, which did not match the declared OneOrMoreDimensions ValueRank. - A provider that cannot push is polled at the Variable's MinimumSamplingInterval rather than being installed once, so a non-pushing provider no longer serves its start-up sample with Good status forever. - The three ISA-95 specific geolocation provider files are removed; their behaviour now comes from the shared seam and its in-memory implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
Review feedback on #4123 objected to ThrowIfNull and IsFinite being public extension methods declared in the System namespace, where they are offered to every consumer of the stack for every expression and compete with the platform's own helpers. They are removed from Polyfills in #4123 and reappear here, in Opc.Ua, next to the coordinate transforms that are their only callers. Pre-existing code in the stack uses the BCL's ArgumentNullException.ThrowIfNull, which is unrelated and untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
…thing Review feedback on #4125. ArrayOf<T> implements INullable, so `default` is the null array, which is a different thing on the wire from an empty one. Browsing for children that do not exist means ''no children'', not ''value not present'', so BrowseChildrenAsync now returns ArrayOf<ReferenceDescription>.Empty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
48423ea to
43a1d7c
Compare
…nector (#4126) **Stack 4/5** — base #4125. Part of the #4096 split; see #4096 for the overall picture. > Review after #4123, #4124 and #4125. ## What this is The generic OPC UA to OpenUSD binding, scene materialization, and the connector that drives a live stage from a running server. ## Contents **Binding (Part 1)** — `Opc.Ua.OpenUsd` and `Opc.Ua.OpenUsd.Server`. A server describes how its Variables drive USD prims, and serves the USD layers it refers to as Part 5 files with a content digest. **Scene materialization (Part 2)** — `Opc.Ua.OpenUsdScene` and `Opc.Ua.OpenUsdScene.Server`, including the USDA writer. - **Escaping fix**: string values, composition-arc references, variant set names and selections, `doc` and prim names were all emitted unescaped, so a value carrying a quote or a newline could author its own layer syntax. Every emit site now goes through the escapers. The tests assert the right property — not that the payload is absent (it legitimately survives inside the escaped literal) but that no payload becomes its own line of layer syntax. **Client** — `Opc.Ua.OpenUsd.Client` streams live values into an `IUsdSink`. `CompositeUsdSink` fans out to several sinks; the file sink interpolates between server samples so motion is continuous rather than stepped at the publishing interval. **Connector** — `tools/Opc.Ua.OpenUsd.Connector` composes a stage from a live server. The optional in-process viewport lives in a separate assembly probed at run time, so the connector still runs — and says why — when it is absent. - **Fix**: the asset cache and PKI root moved off the shared temp directory to `LocalApplicationData`. **Sample** — `PumpDeviceIntegrationServer` gains an OpenUSD representation of the pump. ## Notes for the reviewer - The viewport assembly is net10.0-only. It is what exercises the `Directory.Build.targets` change from 1/5: without it, the `net8.0` leg of the per-TFM CI build fails with `CS0433` because the `System.Threading.Lock` polyfill collides with the real type. - `UsdAssetDelivery` hashes through `SHA256.Create` rather than the static `HashData`, which does not exist on the net472/net48 targets this test project also builds for. ## Validation `dotnet build UA.slnx` — 0 errors, 0 warnings. | Suite | Result | | --- | --- | | Opc.Ua.OpenUsd.Tests | 621 / 0 | | Opc.Ua.Di.Tests | 321 / 0 | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
#4127) **Stack 5/5** — base #4126. Part of the #4096 split; see #4096 for the overall picture. Closes the API work for #3827. > Review after #4123, #4124, #4125 and #4126. This branch is content-identical to the original #4096 branch, so merging the stack lands exactly what #4096 contained. ## The problem this solves OPC 40010 1.02 defines a rich robot **topology** but **no motion verbs at all** — its actuation surface is the SystemOperation and TaskControl state machines plus a Controller `Programs` directory. Meanwhile [URML](https://github.com/URML-MARS/URML) names OPC UA Robotics as its canonical non-ROS substrate and asks for `MoveTo` / `Grasp` / `Release` methods the companion spec does not define, so today it needs a hand-maintained per-deployment NodeId mapping file. This closes that gap in two clearly separated tiers, and never presents the second as standard. ## Contents **Server** — stock `RoboticsNodeManager`, ordered `IRoboticsModelProvider` composition, `IRoboticsConfigurator` code-behind, and validated fluent topology builders for systems, controllers, motion devices, axes, power trains, motors, gears, drives, safety states, software, task controls and task modules, with the correct OPC 40010 semantic references. **Standard operations** — `AddSystemOperation` and `AddTaskControlOperation` drive the Part 16 state machines. An illegal cause is rejected with `BadInvalidState` before the handler runs; a handler returning a bad `ServiceResult` does not move the machine; and a transition already in flight rejects a concurrent one rather than letting both commit. **Programs** — binds the optional `FileDirectoryType` through the reusable `IFileDirectoryBinder` added in 1/5. **Operation conventions** — opt-in and explicitly non-normative `MoveTo` / `MoveJ` / `MoveL` / `Grasp` / `Release` / `PickFrom` / `PlaceAt` / `SwapTool` / `SetOutput` / `CallProgram`, created **only** in an application-owned namespace — the OPC UA, DI, IA and Robotics namespaces are rejected — plus a generic `AddOperation<TRequest, TResponse>` extension point. **Client** — `RoboticsClient` extends the DI client, is subtype aware throughout, and provides typed topology snapshots, the standard operation and task-control clients, `ProgramsAsync`, streaming observation and a DI factory. Verbs resolve by BrowseName, so no NodeId mapping file is needed. **Sample** — `MinimalRobotServer` composes five models (Robotics, IA, DI, OpenUSD, RSL/GPOS) purely through `IRoboticsModelProvider` contributions, which is the real proof that the stock hosting is sufficient. It follows the published KUKA KR 16-2 kinematics and axis limits with an eased pick-and-place cycle. Also restores the vendor-subtype scanner test held back from 2/5, which uses the Robotics NodeSet as its fixture. ## Defects found and fixed along the way - **`ControllerType.CurrentUser`** is Mandatory in OPC 40010 but was neither built nor validated. Now has a builder and a validation error. - **`MotionDeviceType.TaskControlReference`** was absent because it must target a `TaskControlOperationType` instance that did not exist. Now wired. ## Validation `dotnet build UA.slnx` — 0 errors, 0 warnings. | Suite | Result | | --- | --- | | Opc.Ua.Robotics.Tests | 103 / 0 | | Opc.Ua.OpenUsd.Tests | 635 / 0 | | Opc.Ua.Di.Tests | 342 / 0 | | Opc.Ua.SourceGeneration.Tests | 94 / 0 | | Opc.Ua.Aot.Tests (NativeAOT) | 120 / 0 | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
Stack 3/5 — base #4124. Part of the #4096 split; see #4096 for the overall picture.
What this is
The OPC 10000-210 Relative Spatial Location and OPC 10000-211 Global Positioning companion models, with their server and client libraries, plus the ISA-95 migration onto the shared geolocation seam.
Why it is one change
A deployment writes one provider implementation and both OPC 10000-211 and OPC 10030 are served from it. The specifications disagree on representation, not on content:
GlobalLocationDataTypeand validates the EPSG code against the configured CRS.Stringarray that OPC 10030 §8.3.2.2 requires.Splitting them would mean two provider interfaces for one physical sensor, which is exactly what this avoids. The seam itself lives in
Opc.Ua.Serverand landed in 1/5.Contents
Opc.Ua.Positioning,.Serverand.Client.Opc.Ua.ISA95.Servermoves ontoIGeoLocationProvider; the three ISA-95-specific provider files are removed because the shared seam and its in-memory implementation replace them.ValueRankOneOrMoreDimensions. It now writesString[].MinimumSamplingIntervalrather than installed once, so a non-pushing provider no longer serves its start-up sample withGoodstatus forever.samples/MinimalIsa95Server.Validation
dotnet build UA.slnx— 0 errors.