Browsing finds the variable and its relationships, but it does not return the variable’s value data type. An AnalogItemType or TwoStateDiscrete result identifies the variable’s type definition, not whether a later subscription value will be an integer, double, scalar, or array.
Approach comparison
| Approach | What it identifies | Can it determine the subscribed value type? | Decision |
|---|---|---|---|
| Inspect the browse result | Node ID, reference type, node class, and type definition | No. TypeDefinition describes the variable model, while ReferenceTypeId describes its relationship to another node. |
Use for discovery and hierarchy only. |
Pass the variable Node ID to UADataType.FindDataType
|
Attempts to interpret the variable instance as a DataType node | No. The method is receiving the wrong Node ID. | Replace this call path. |
Read UAAttributeId.DataType, then map its returned Node ID |
The variable’s declared OPC UA data type | Yes for types with a supported .NET mapping. | Recommended for configuration generation. |
Also read UAAttributeId.ValueRank and UAAttributeId.ArrayDimensions
|
Whether the value is scalar or array-shaped, plus declared dimensions where supplied | Yes. These attributes complete the value-shape definition. | Required when array values are possible. |
The commissioning rule is direct: browse to discover candidate variables, read their type-related attributes, and pass the DataType attribute’s value—not the variable’s Node ID—to UADataType.FindDataType.
Recommended commissioning sequence
- Browse from the selected base node and retain the Node ID for every candidate data node. Confirm that each record contains the actual browsed Node ID before moving on.
- Classify the node with
NodeClass. Process value-bearing variable nodes through the type-resolution path; do not infer their value type from the CLR type of the node-class enumeration. - Read
UAAttributeId.DataTypefrom each variable. Confirm that the read returns a DataType Node ID rather than the variable instance Node ID. - Pass that returned DataType Node ID to
UADataType.FindDataType. Confirm that the outputTypeis non-null before storing a .NET type mapping. - Read
UAAttributeId.ValueRank. Confirm whether the declared value shape is scalar or array-like before selecting serialization and subscription handling. - When the shape is an array, read
UAAttributeId.ArrayDimensions. Store the returned dimension information without inventing dimensions when the server does not declare them. - Write the DataType Node ID, mapping status, value rank, and array dimensions into the configuration record. Keep these fields separate from
TypeDefinition. - Create a test subscription or perform a value read for selected nodes. Compare the received value and shape with the stored metadata before releasing the generated configuration.
Attribute acquisition
The existing browse record already supplies useful identity and hierarchy fields. The correction belongs after discovery: issue attribute reads against element.NodeId. The following C#-style pattern deliberately leaves the client-specific read wrapper as an application adapter because the exact read method is not shown:
// variableNodeId is the NodeId of the browsed variable.
var dataTypeId = ReadAttribute(
variableNodeId,
UAAttributeId.DataType);
Type typeDesc;
var mapped = UADataType.FindDataType(dataTypeId, out typeDesc);
var valueRank = ReadAttribute(
variableNodeId,
UAAttributeId.ValueRank);
var arrayDimensions = ReadAttribute(
variableNodeId,
UAAttributeId.ArrayDimensions);
ReadAttribute represents the project’s normal OPC UA attribute-read operation. Its first argument remains the variable Node ID. Only the value returned from the DataType attribute becomes the input to UADataType.FindDataType.
Do not substitute descriptor.NodeId directly into the mapping call. That descriptor identifies the browsed instance. A DataType Node ID identifies the type definition used for the variable’s value encoding.
Browse metadata versus value metadata
| Observed field or symptom | Actual meaning | Required action |
|---|---|---|
element.TypeDefinition reports AnalogItemType
|
The variable follows that information-model type definition. | Read UAAttributeId.DataType to determine its value type. |
element.TypeDefinition reports TwoStateDiscrete
|
The node has a two-state discrete model. | Do not use the type definition as the CLR value type; read the DataType attribute. |
ReferenceTypeId is UAReferenceTypeIds.Organizes
|
The reference organizes one node under another. | Continue discovery according to the application’s hierarchy rules. |
ReferenceTypeId is UAReferenceTypeIds.HasComponent
|
The target is a component of another node. | Record the relationship, then resolve value metadata separately. |
element.NodeClass.GetTypeCode() returns a result |
The result describes the CLR representation of the node-class value itself. | Do not store it as the variable’s subscribed value type. |
UADataType.FindDataType returns no CLR type |
The supplied ID was wrong or the UA type has no direct mapping supported by the library. | Validate the attribute-read target and preserve the UA DataType Node ID for unmapped types. |
DataType mapping and fallback handling
The primary fault in the original mapping logic is this call:
UADataType.FindDataType(descriptor.NodeId, out typeDesc);
Replace descriptor.NodeId with the Node ID obtained by reading UAAttributeId.DataType. The mapping result then answers a different and valid question: which .NET Type, if any, corresponds to the declared OPC UA data type?
A null mapping must remain distinguishable from a string mapping. The fallback below does not provide a safe unknown-type policy:
if (typeDesc == null)
typeDesc = Type.GetType("string");
Type.GetType("string") does not resolve the C# keyword as a normal runtime type name, so it can still return null. Replacing it with typeof(string) would produce a real string type, but it would also falsely classify every unmapped UA type as text. Record an explicit mapping failure instead.
if (typeDesc == null)
{
// Preserve dataTypeId and mark the CLR mapping as unresolved.
// Do not silently classify the value as string.
}
QuickOPC does not provide direct .NET equivalents through this mapping path for every UA type, particularly custom data types. Keep the original DataType Node ID in the configuration so later decoding logic can distinguish an unsupported mapping from a failed attribute read.
Scalar and array decisions
UAAttributeId.DataType identifies the element type, but it does not by itself describe the complete value shape. Two variables can declare the same element type while one returns a scalar and the other returns an array. That difference changes configuration validation, serialization, buffer handling, and downstream tag binding.
- Read
UAAttributeId.ValueRankafter resolving the DataType Node ID. - If the value rank indicates an array form, read
UAAttributeId.ArrayDimensions. - Store the raw attribute results with the node record. Do not collapse an array declaration into its element’s .NET type name.
- At subscription time, validate both the received element representation and its shape. A correct element mapping with an unexpected rank is still a configuration mismatch.
When the server supplies no fixed dimensions, retain that state rather than generating a size. The subscription consumer must then accept the server-reported runtime shape within the application’s own limits.
Configuration record design
A future subscription configuration needs enough information to recreate both node selection and type handling. Keep model metadata and value metadata in separate fields.
| Configuration field | Source | Purpose |
|---|---|---|
| Variable Node ID | Browse result | Selects the node for later reads or subscriptions. |
| Namespace information | Node descriptor | Preserves the node’s namespace identity. |
| Node class | Browse result | Separates variable candidates from other node classes. |
| Type definition | Browse result | Describes the information-model role, such as AnalogItemType. |
| DataType Node ID | UAAttributeId.DataType |
Defines the declared value element type. |
| .NET mapping status | UADataType.FindDataType |
Records whether the client library produced a CLR type. |
| Value rank | UAAttributeId.ValueRank |
Distinguishes scalar and array-shaped values. |
| Array dimensions | UAAttributeId.ArrayDimensions |
Captures declared dimensions when applicable. |
Perform file output only after all required reads for a node complete. The browsing routine shown uses async void; changing asynchronous work to a task-returning flow allows the caller to await completion and observe read or file-write failures. Otherwise, a configuration export can appear complete while attribute operations are still running or have failed outside the caller’s normal error path.
Verification criteria
- Select one scalar node and one array node when both are available. Read their
DataType,ValueRank, andArrayDimensionsattributes. - Check that the saved variable Node ID differs in purpose from the saved DataType Node ID. The first selects the value source; the second defines its declared element type.
- Run
UADataType.FindDataTypewith the DataType Node ID and record the returned mapping status. Do not move on if the code still passes the variable Node ID. - Subscribe to or read the selected variables and compare the runtime value’s element representation and shape with the saved metadata.
- For an unmapped custom type, verify that the configuration retains its DataType Node ID and reports an unresolved CLR mapping rather than
string.
FAQ
What happens if I pass the variable Node ID to UADataType.FindDataType?
The method receives a variable instance identifier instead of a DataType identifier, so it cannot perform the intended .NET type mapping. Read UAAttributeId.DataType first and pass the Node ID returned by that attribute.
What happens if the node is an AnalogItemType?
AnalogItemType describes the variable’s information-model type, not whether the value is an integer or double. Resolve the value type through UAAttributeId.DataType.
What happens if UADataType.FindDataType returns null?
First confirm that its input came from the variable’s DataType attribute. If the ID is correct, preserve that ID and flag the CLR mapping as unresolved because custom UA data types may lack a direct .NET equivalent.
What happens if I default an unknown OPC UA type to string?
The configuration can misclassify a structured or otherwise unmapped value as text. Also, Type.GetType("string") can return null; store an explicit unresolved state instead of applying a string fallback.
What happens if I read DataType but not ValueRank?
You may identify the element type while missing that the subscribed value is an array. Read UAAttributeId.ValueRank, read UAAttributeId.ArrayDimensions when applicable, then verify the received value against both the saved element type and shape.