On Galaxy deploy changes, only the affected gobject subtrees are torn down and rebuilt instead of destroying the entire address space. Unchanged nodes, subscriptions, and alarm tracking continue uninterrupted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
235 lines
11 KiB
Markdown
235 lines
11 KiB
Markdown
# OPC UA Server — Component Requirements
|
|
|
|
Parent: [HLR-001](HighLevelReqs.md#hlr-001-opc-ua-server), [HLR-002](HighLevelReqs.md#hlr-002-galaxy-hierarchy-as-opc-ua-address-space), [HLR-004](HighLevelReqs.md#hlr-004-data-type-mapping)
|
|
|
|
## OPC-001: Server Endpoint
|
|
|
|
The OPC UA server shall listen on a configurable TCP port (default 4840) using the OPC Foundation .NET Standard stack.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Server starts and accepts TCP connections on the configured port.
|
|
- Port is read from `appsettings.json` under `OpcUa:Port`; defaults to 4840 if absent.
|
|
- Endpoint URL format: `opc.tcp://<hostname>:<port>/LmxOpcUa`.
|
|
- If the port is in use at startup, log an Error and fail to start (do not silently pick another port).
|
|
- Security policy: None (no certificate validation). This is an internal plant-floor service.
|
|
|
|
### Details
|
|
|
|
- Configurable items: port (default 4840), endpoint path (default `/LmxOpcUa`), server application name (default `LmxOpcUa`).
|
|
- Server shall use the `OPCFoundation.NetStandard.Opc.Ua.Server` NuGet package.
|
|
- On startup, log the endpoint URL at Information level.
|
|
|
|
---
|
|
|
|
## OPC-002: Address Space Structure
|
|
|
|
The server shall create folder nodes for areas and object nodes for automation objects, organized in the same parent-child hierarchy as the Galaxy.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- The root folder node has BrowseName `ZB` (hardcoded Galaxy name).
|
|
- Objects where `is_area = 1` are created as FolderType nodes (organizational).
|
|
- Objects where `is_area = 0` are created as BaseObjectType nodes.
|
|
- Parent-child relationships use Organizes references (for areas) and HasComponent references (for contained objects).
|
|
- A client browsing Root → Objects → ZB → DEV → TestArea → TestMachine_001 → DelmiaReceiver sees the same structure as `gr/layout.md`.
|
|
|
|
### Details
|
|
|
|
- NodeIds use a string-based identifier scheme: `ns=1;s=<tag_name>` for object nodes, `ns=1;s=<tag_name>.<attribute_name>` for variable nodes.
|
|
- Infrastructure objects (AppEngines, Platforms) are included in the tree but may have no variable children.
|
|
- When `contained_name` is null or empty, fall back to `tag_name` as the BrowseName.
|
|
|
|
---
|
|
|
|
## OPC-003: Variable Nodes for Attributes
|
|
|
|
Each user-defined attribute on a deployed object shall be represented as an OPC UA variable node under its parent object node.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Each row from `attributes.sql` creates one variable node under the matching object node (matched by `gobject_id`).
|
|
- Variable node BrowseName and DisplayName are set to `attribute_name`.
|
|
- Variable node stores `full_tag_reference` as its runtime MXAccess address.
|
|
- Variable node AccessLevel is set based on the attribute's `security_classification` per the mapping in `gr/data_type_mapping.md`.
|
|
- FreeAccess (0), Operate (1), Tune (4), Configure (5) → AccessLevel = CurrentRead | CurrentWrite (3).
|
|
- SecuredWrite (2), VerifiedWrite (3), ViewOnly (6) → AccessLevel = CurrentRead (1).
|
|
- Objects with no user-defined attributes still appear as object nodes with zero children.
|
|
|
|
### Details
|
|
|
|
- Security classification determines the OPC UA AccessLevel and UserAccessLevel attributes on each variable node. The OPC UA stack enforces read-only access for nodes with CurrentRead-only access level.
|
|
- Attributes whose names start with `_` are already filtered by the SQL query.
|
|
|
|
---
|
|
|
|
## OPC-004: Browse Name Translation
|
|
|
|
Browse names shall use contained names (human-readable, scoped to parent). The server shall internally translate browse paths to tag_name references for MXAccess operations.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- A variable node browsed as `ZB/DEV/TestArea/TestMachine_001/DelmiaReceiver/DownloadPath` correctly translates to MXAccess reference `DelmiaReceiver_001.DownloadPath`.
|
|
- Translation uses the `tag_name` stored on the parent object node, not the browse path.
|
|
- No runtime path parsing — the mapping is baked into each node at build time.
|
|
|
|
### Details
|
|
|
|
- Each variable node stores its `full_tag_reference` (e.g., `DelmiaReceiver_001.DownloadPath`) at address-space build time. Read/write operations use this stored reference directly.
|
|
|
|
---
|
|
|
|
## OPC-005: Data Type Mapping
|
|
|
|
Variable nodes shall use OPC UA data types mapped from Galaxy mx_data_type values per the mapping in `gr/data_type_mapping.md`.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Every `mx_data_type` value in the mapping table produces the correct OPC UA DataType NodeId on the variable node.
|
|
- Unknown/unmapped `mx_data_type` values default to String (i=12).
|
|
- ElapsedTime (type 7) maps to Double representing seconds.
|
|
|
|
### Details
|
|
|
|
- Full mapping table in `gr/data_type_mapping.md`.
|
|
- DateTime conversion: Galaxy may store local time; convert to UTC for OPC UA.
|
|
- LocalizedText (type 15): use empty locale string with the text value.
|
|
|
|
---
|
|
|
|
## OPC-006: Array Support
|
|
|
|
Attributes marked as arrays shall have ValueRank=1 and ArrayDimensions set to the attribute's array_dimension value.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- `is_array = 1` produces ValueRank = 1 (OneDimension) and ArrayDimensions = `[array_dimension]`.
|
|
- `is_array = 0` produces ValueRank = -1 (Scalar) and no ArrayDimensions.
|
|
- MXAccess reference for array attributes uses `tag_name.attribute[]` (whole array) format.
|
|
|
|
### Details
|
|
|
|
- Individual array element access (`tag_name.attribute[n]`) is not required for initial implementation. Whole-array read/write only.
|
|
- If `array_dimension` is null or 0 when `is_array = 1`, log a Warning and default to ArrayDimensions = [0] (variable-length).
|
|
|
|
---
|
|
|
|
## OPC-007: Read Operations
|
|
|
|
The server shall fulfill OPC UA Read requests by reading the corresponding tag value from MXAccess using the tag_name.AttributeName reference.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- OPC UA Read request for a variable node results in a read via MXAccess using the node's stored `full_tag_reference`.
|
|
- Returned value is converted from the COM variant to the OPC UA data type specified on the node.
|
|
- OPC UA StatusCode reflects MXAccess quality: Good maps to Good, Bad/Uncertain map appropriately.
|
|
- If MXAccess is not connected, return StatusCode = Bad_NotConnected.
|
|
- Read timeout: configurable, default 5 seconds. On timeout, return Bad_Timeout.
|
|
|
|
### Details
|
|
|
|
- Prefer cached subscription-delivered values over on-demand reads to reduce COM round-trips.
|
|
- If no subscription is active for the tag, perform an on-demand read (AddItem, AdviseSupervisory, wait for first OnDataChange, then UnAdvise/RemoveItem).
|
|
- Concurrency: semaphore-limited to configurable max (default 10) concurrent MXAccess operations.
|
|
|
|
---
|
|
|
|
## OPC-008: Write Operations
|
|
|
|
The server shall fulfill OPC UA Write requests by writing to the corresponding tag via MXAccess.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- OPC UA Write request results in an MXAccess `Write()` call with completion confirmed via `OnWriteComplete()` callback.
|
|
- Write timeout: configurable, default 5 seconds. On timeout, log Warning and return Bad_Timeout.
|
|
- MXSTATUS_PROXY with `success = 0` causes the OPC UA write to return Bad_InternalError with the detail message.
|
|
- MXAccess errors 1008 (no permission), 1012 (secured write), 1013 (verified write) return Bad_UserAccessDenied.
|
|
- Write to a non-existent tag returns Bad_NodeIdUnknown.
|
|
- The server shall attempt to convert the written value to the expected Galaxy data type before passing to Write().
|
|
|
|
### Details
|
|
|
|
- Write uses security classification -1 (no security). Galaxy runtime handles security enforcement.
|
|
- Write sequence: uses existing subscription handle if available, otherwise AddItem + AdviseSupervisory + Write + await OnWriteComplete + cleanup.
|
|
- Concurrent write limit: same semaphore as reads (configurable, default 10).
|
|
|
|
---
|
|
|
|
## OPC-009: Subscriptions
|
|
|
|
The server shall support OPC UA subscriptions by mapping them to MXAccess advisory subscriptions and forwarding data change notifications.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- OPC UA CreateMonitoredItems results in MXAccess `AdviseSupervisory()` subscriptions for the requested tags.
|
|
- Data changes from `OnDataChange` callback are forwarded as OPC UA notifications to all subscribed clients.
|
|
- Shared subscriptions: if two OPC UA clients subscribe to the same tag, only one MXAccess subscription exists (ref-counted).
|
|
- Last subscriber unsubscribing triggers UnAdvise/RemoveItem on the MXAccess side.
|
|
- After MXAccess reconnect, all active MXAccess subscriptions are re-established automatically.
|
|
|
|
### Details
|
|
|
|
- Publishing interval from the OPC UA subscription request is honored on the OPC UA side; MXAccess delivers changes as fast as it receives them.
|
|
- OPC UA quality mapping from MXAccess quality integers: 192+ = Good, 64-191 = Uncertain, 0-63 = Bad.
|
|
- OnDataChange with MXSTATUS_PROXY failure: deliver notification with Bad quality to subscribed clients.
|
|
|
|
---
|
|
|
|
## OPC-010: Address Space Rebuild
|
|
|
|
When a Galaxy deployment change is detected, the server shall rebuild the address space without dropping existing OPC UA client connections where possible.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- When Galaxy Repository detects a deployment change, the OPC UA address space is updated.
|
|
- Only changed gobject subtrees are torn down and rebuilt; unchanged nodes, subscriptions, and alarm tracking remain intact.
|
|
- Existing OPC UA client sessions are preserved — clients stay connected.
|
|
- Subscriptions for tags on unchanged objects continue to work without interruption.
|
|
- Subscriptions for tags that no longer exist receive a Bad_NodeIdUnknown status notification.
|
|
- Sync is logged at Information level with the number of changed gobjects.
|
|
|
|
### Details
|
|
|
|
- Uses incremental subtree sync: compares previous hierarchy+attributes with new, identifies changed gobject IDs, expands to include child subtrees, tears down only affected subtrees, and rebuilds them.
|
|
- First build (no cached state) performs a full build.
|
|
- If no changes are detected, the sync is a no-op (logged and skipped).
|
|
- Alarm tracking and MXAccess subscriptions for unchanged objects are not disrupted.
|
|
- Falls back to full rebuild behavior if the entire hierarchy changes.
|
|
|
|
---
|
|
|
|
## OPC-011: Server Diagnostics Node
|
|
|
|
The server shall expose a ServerStatus node under the standard OPC UA Server object with ServerState, CurrentTime, and StartTime. This is required by the OPC UA specification for compliant servers.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- ServerState reports Running during normal operation.
|
|
- CurrentTime returns the server's current UTC time.
|
|
- StartTime returns the UTC time when the service started.
|
|
|
|
---
|
|
|
|
## OPC-012: Namespace Configuration
|
|
|
|
The server shall register a namespace URI at namespace index 1. All application-specific NodeIds shall use this namespace.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Namespace URI: `urn:ZB:LmxOpcUa` (Galaxy name is configurable).
|
|
- All object and variable NodeIds created from Galaxy data use namespace index 1.
|
|
- Standard OPC UA nodes remain in namespace 0.
|
|
|
|
---
|
|
|
|
## OPC-013: Session Management
|
|
|
|
The server shall support multiple concurrent OPC UA client sessions.
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Maximum concurrent sessions: configurable, default 100.
|
|
- Session timeout: configurable, default 30 minutes of inactivity.
|
|
- Expired sessions are cleaned up and their subscriptions removed.
|
|
- Session count is reported to the status dashboard.
|