# 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://:/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=` for object nodes, `ns=1;s=.` 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.