If you are using the OPCFoundation.Netstandard.OPC.Ua SDK.
I can provide you some examples, since i just had to take a deep dive into this specific topic.
It is quite a bit complicated at first, since there isn't really any form of full documentation.
Since the answer above me explained setting up the session pretty well already, i wanted to supply some more examples, that worked for me.
When getting any NodeId
you need to pass in the symbolicsNamespace Id.
Since i'm using a Siemens PLC in the background, i know that this namespace is always called "SYM:". Once you know the name of your node, you could use a method like this one:
public Task<ushort> GetSymbolicsNameSpaceId()
{
_session.FetchNamespaceTables();
var namespaces = _session.NamespaceUris.ToArray();
ushort index = (ushort)Array.IndexOf(namespaces, "SYM:");
return Task.FromResult(index);
}
Firstly, if you want to check the current server status before sending or reading anything, you can do that, by getting the Variables.Server_ServerStatus
NodeId and pass it into a regular _session.ReadValue()
to receive the corresponding DataValue
.
public Task<ServerStatusDataType> GetServerStatus()
{
// Get the current DataValue object for the ServerStatus node
NodeId nodeId = new NodeId(Variables.Server_ServerStatus);
DataValue dataValue = _session.ReadValue(nodeId);
// Unpack the ExtensionObject that the DataValue contains, then return ServerStatusDataType object
// that represents the current server status
ExtensionObject extensionObject = (ExtensionObject)dataValue.Value;
ServerStatusDataType serverStatus = (ServerStatusDataType)extensionObject.Body;
return Task.FromResult(serverStatus);
}
To get a value from a specific OPC Server variable, you need to get the corresponding NodeId first, which can be done by simply calling the NodeId
constructor with the NodeIdentifier, VariableName and the symbolics namespaceId:
public Task<DataValue>? GetValue(string nodeName, string varName, ushort namespaceIndex)
{
// To read a value, you require the node's ID. This can be either its unique integer ID, or a string
// identifier along with the namespace which that identifier belongs to. Integer IDs are most useful
// for acquiring nodes defined in the OPC UA standard, such as the ServerStatus node. The namespace
// of a tag may differ depending on the OPC server being used, with KEPServer having a tag namespace
// of 2. The only namespace that is guaranteed to remain the same is namespace 0, which contains the
// nodes defined in the OPC UA standard.
NodeId nodeId = new NodeId($"{nodeName}.{varName}", namespaceIndex);
try
{
return Task.FromResult(_session.ReadValue(nodeId));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
}
}
Writing a value was a bit more of a challenge to understand, but i'm gonna try to explain it a bit further.
Since the PLC behind the OPC Server has it's own datatypes which do not directly correspond to our C# datatypes, we have to cast the value into the correct corresponding type, here is my full implementation of writing a variable:
// Get type of variable in OPC Server which should be written and cast the value before actually writing it
public Task<bool> WriteValue(string nodeName, string varName, ushort namespaceIndex, string value)
{
try
{
NodeId nodeId = new NodeId($"{nodeName}.{varName}", namespaceIndex);
// Read the node you want to write to
var nodeToWrIteTo = _session.ReadValue(nodeId);
// Get type of the specific variable you want to write
BuiltInType type = nodeToWrIteTo.WrappedValue.TypeInfo.BuiltInType;
// Get the corresponding C# datatype
Type csType = Type.GetType($"System.{type}");
// Cast the value
var castedValue = Convert.ChangeType(value, csType);
// Create a WriteValue object with the new value
var writeValue = new WriteValue
{
NodeId = nodeId,
AttributeId = Attributes.Value,
Value = new DataValue(new Variant(castedValue))
};
// Write the new value to the node
_session.Write(null, new WriteValueCollection { writeValue }, out StatusCodeCollection statusCodeCollection, out DiagnosticInfoCollection diagnosticInfo);
// Check the results to make sure the write succeeded
if (statusCodeCollection[0].Code != Opc.Ua.StatusCodes.Good)
{
return Task.FromResult(false);
}
return Task.FromResult(true);
}
catch (Exception)
{
return Task.FromResult(false);
}
}
Good Luck!
P.S.: This is still a poc on my end, so do not rely on my errorhandling.