-
Notifications
You must be signed in to change notification settings - Fork 5.8k
feat(common.opcua): Add string configuration option for node ID #18232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…ration option
- Add support for specifying OPC UA node IDs using the standard string format
(e.g., "ns=0;i=2262" or "nsu=http://...;s=Name") as an alternative to
specifying namespace, identifier_type, and identifier separately.
- This simplifies configuration and matches the format used in OPC UA browsers.
Example usage:
{name="ProductUri", node_id="ns=0;i=2262"}
Instead of:
{name="ProductUri", namespace="0", identifier_type="i", identifier="2262"}
srebhan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @skartikey! Some comments from my side.
| // NodeSettings describes how to map from a OPC UA node to a Metric | ||
| type NodeSettings struct { | ||
| FieldName string `toml:"name"` | ||
| NodeIDStr string `toml:"node_id"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we name this option just id? Asking because it kind of sounds strange if you have node_id in a nodes section...
Same below...
| // nodeIDParts holds the components of a parsed OPC UA node ID string | ||
| type nodeIDParts struct { | ||
| namespace string | ||
| namespaceURI string | ||
| identifierType string | ||
| identifier string | ||
| } | ||
|
|
||
| // parseNodeIDString parses an OPC UA node ID string (e.g., "ns=0;i=2262" or "nsu=http://...;s=Name") | ||
| // and returns the parsed components. | ||
| func parseNodeIDString(nodeIDStr string) (nodeIDParts, error) { | ||
| var result nodeIDParts | ||
|
|
||
| // Split on semicolon to get namespace part and identifier part | ||
| parts := strings.SplitN(nodeIDStr, ";", 2) | ||
| if len(parts) != 2 { | ||
| return result, fmt.Errorf("invalid node ID format %q: expected 'ns=X;Y=Z' or 'nsu=URI;Y=Z'", nodeIDStr) | ||
| } | ||
|
|
||
| // Parse namespace part (ns= or nsu=) | ||
| nsPart := parts[0] | ||
| switch { | ||
| case strings.HasPrefix(nsPart, "ns="): | ||
| result.namespace = strings.TrimPrefix(nsPart, "ns=") | ||
| case strings.HasPrefix(nsPart, "nsu="): | ||
| result.namespaceURI = strings.TrimPrefix(nsPart, "nsu=") | ||
| default: | ||
| return result, fmt.Errorf("invalid node ID format %q: namespace must start with 'ns=' or 'nsu='", nodeIDStr) | ||
| } | ||
|
|
||
| // Parse identifier part (i=, s=, g=, or b=) | ||
| idPart := parts[1] | ||
| if len(idPart) < 2 || idPart[1] != '=' { | ||
| return result, fmt.Errorf("invalid node ID format %q: identifier must be in format 'X=value'", nodeIDStr) | ||
| } | ||
|
|
||
| result.identifierType = string(idPart[0]) | ||
| result.identifier = idPart[2:] | ||
|
|
||
| // Validate identifier type | ||
| switch result.identifierType { | ||
| case "i", "s", "g", "b": | ||
| // Valid | ||
| default: | ||
| return result, fmt.Errorf("invalid identifier type %q in node ID %q: expected i, s, g, or b", result.identifierType, nodeIDStr) | ||
| } | ||
|
|
||
| return result, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this? Can't we use the library function for parsing?
| // SetFromNodeIDString parses the NodeIDStr field and populates the individual fields. | ||
| // Returns an error if NodeIDStr is set but invalid, or if both NodeIDStr and individual fields are set. | ||
| func (tag *NodeSettings) SetFromNodeIDString() error { | ||
| if tag.NodeIDStr == "" { | ||
| return nil | ||
| } | ||
|
|
||
| // Check for conflicting configuration | ||
| if tag.Namespace != "" || tag.NamespaceURI != "" || tag.IdentifierType != "" || tag.Identifier != "" { | ||
| return fmt.Errorf("node %q: cannot specify both 'node_id' and individual fields (namespace/namespace_uri/identifier_type/identifier)", tag.FieldName) | ||
| } | ||
|
|
||
| parsed, err := parseNodeIDString(tag.NodeIDStr) | ||
| if err != nil { | ||
| return fmt.Errorf("node %q: %w", tag.FieldName, err) | ||
| } | ||
|
|
||
| tag.Namespace = parsed.namespace | ||
| tag.NamespaceURI = parsed.namespaceURI | ||
| tag.IdentifierType = parsed.identifierType | ||
| tag.Identifier = parsed.identifier | ||
| return nil | ||
| } | ||
|
|
||
| // SetFromNodeIDString parses the NodeIDStr field and populates the individual fields. | ||
| // Returns an error if NodeIDStr is set but invalid, or if both NodeIDStr and individual fields are set. | ||
| func (e *EventNodeSettings) SetFromNodeIDString() error { | ||
| if e.NodeIDStr == "" { | ||
| return nil | ||
| } | ||
|
|
||
| // Check for conflicting configuration | ||
| if e.Namespace != "" || e.NamespaceURI != "" || e.IdentifierType != "" || e.Identifier != "" { | ||
| return errors.New("cannot specify both 'node_id' and individual fields (namespace/namespace_uri/identifier_type/identifier)") | ||
| } | ||
|
|
||
| parsed, err := parseNodeIDString(e.NodeIDStr) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| e.Namespace = parsed.namespace | ||
| e.NamespaceURI = parsed.namespaceURI | ||
| e.IdentifierType = parsed.identifierType | ||
| e.Identifier = parsed.identifier | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would go the other way and directly parse this to a node-id type as that's what we need later on.
plugins/inputs/opcua/README.md
Outdated
| > **Note:** Use either `node_id` OR the combination of | ||
| > `namespace`/`namespace_uri` + `identifier_type` + `identifier`. | ||
| > Do not mix both formats for the same node. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use markdown alerts for this!
| > **Note:** Use either `node_id` OR the combination of | ||
| > `namespace`/`namespace_uri` + `identifier_type` + `identifier`. | ||
| > Do not mix both formats for the same node. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
|
Download PR build artifacts for linux_amd64.tar.gz, darwin_arm64.tar.gz, and windows_amd64.zip. 📦 Click here to get additional PR build artifactsArtifact URLs |
Summary
Adds support for specifying OPC UA node IDs using the standard string format (e.g., ns=0;i=2262 or nsu=http://...;s=Name) as an alternative to specifying namespace, identifier_type, and identifier separately.
New style (recommended for simplicity):
Existing style (still fully supported):
Supported node ID formats:
Checklist
Related issues
resolves #12396