6.10.94-stable

OPC UA - IEC62541 library

This library is available for GreyCat Pro users

@library("opcua", "0.0.0");

OPC UA, or Open Platform Communications Unified Architecture, is a standardized communication protocol for industrial automation applications. It provides a platform-independent, service-oriented architecture that enables secure and reliable data exchange between various industrial devices and systems. OPC UA is used to facilitate interoperability and communication in industrial environments, allowing different types of machines, sensors, and control systems to seamlessly exchange data regardless of the underlying hardware or software platforms. It ensures compatibility, scalability, and security in distributed systems, making it a preferred choice for implementing Industrial Internet of Things (IIoT) and Industry 4.0 solutions. In that regard, we provide for users of GreyCat Pro, an OPC UA Client allowing the seamless connection to OPC UA devices to retrieve their values, without the need to leave their GCL code and use external tools!

OpcuaClient

The OpcuaClient type is responsible for establishing a connection to an OPC UA device.

Properties

When instantiating this type, the following properties are available:

  • host: String: Specifies the hostname or IP address of the OPC UA device.
  • port: int: Indicates the OPC UA port number (usually 4840).
  • path: String?: (Optional) Represents the optional path to the required endpoint. If not provided, it defaults to /.
  • security_mode: OpcuaSecurityMode: Specifies the OPC UA security mode to be applied to the connection.
  • security_policy: OpcuaSecurityPolicy?: (Optional) Specifies the OPC UA security policy to be applied to the connection, if required.
  • credentials: OpcuaCredentials?: (Optional) Provides credentials for authentication, if necessary.
  • certificate: OpcuaCertificate?: (Optional) Optionally includes a client certificate for authentication purposes.

Methods

The following methods are available:

  • fn read(nodeId: String): any?: Read the current value of an OPC UA node
  • fn read_all(nodeIds: Array<String>): Array<any?>: Read the current value of a batch of OPC UA nodes
  • fn read_with_time(nodeId: String):OpcuaValueDetails?: Read the current value of a OPC UA node and returns an object with server time and source time
  • fn read_all_with_time(nodeIds: Array<String>): Array<OpcuaValueDetails?>: Read the current value of a batch of OPC UA nodes and returns a collection of objects with server time and source time
  • fn read_history(nodeId: String, from: time?, to:time?):Array<OpcuaValueDetails>?: Read the history of a node (if available, through an Historian for example), and returns a collection of objects with server time and source time
  • fn write(nodeId: String, value:any): Write a value to a node
  • fn call(nodeId: String, parameters: Array<any?>): any?: Perform a OPC UA RPC call with parameters

Addressing OPC UA nodes

To read the current value of a specified NodeID, it must be provided in its string representation, following the OPC UA encoding format: ns=<namespaceindex>;<type>=<value>. For example, ns=0;i=2258 or ns=1;s=string.id.

  • ns: Indicates the namespace index of the NodeID.
  • <type>: Represents the type of the NodeID. It can be i for numeric NodeIDs, s for string NodeIDs, g for Guid or b for Opaque/ByteString identifier.
  • <value>: Specifies the value of the NodeID.

Read: Type mapping

The following table illustrates the OPC UA types supported by GreyCat and their corresponding mappings:

OPC UA Type GreyCat mapping
Boolean bool
SByte int
Byte int
Int16 int
UInt16 int
Int32 int
UInt32 int
Int64 int
UInt64 int
Float float
Double float
String String
DateTime time
ByteString String
XmlElement String
Guid String
StatusCode String
NodeId String
ExpandedNodeId String
QualifiedName String
LocalizedText String
ExtensionObject READING NOT SUPPORTED
DataValue READING NOT SUPPORTED
Variant READING NOT SUPPORTED
DiagnosticInfo READING NOT SUPPORTED

OPC UA arrays are converted into GreyCat arrays, while OPC UA matrices are transformed into GreyCat arrays of arrays.

Writing values

In the context of Industry 4.0, the capability to write data back to a machine after data analytics or machine learning algorithms have been applied serves multiple essential purposes. Firstly, it enables proactive maintenance strategies by allowing machines to be stopped or alarms raised immediately upon detection of anomalies or potential malfunctions. Secondly, providing aggregated data or results from machine learning algorithms directly to the machine enables informed decision-making at the operational level, empowering machines to adjust their behavior autonomously based on insights derived from the data. Moreover, fine-tuning machine parameters based on the analyzed data ensures optimal performance and efficiency, as adjustments can be made in real-time to adapt to changing production conditions or quality requirements.

OpcuaClient provides a write method to write data on a OPC UA device. The prerequisites are that the node must be writable, the OPC Ua user must have write permissions on the device and the provided value must match the node type.

write(nodeId: String, value:any)

The following types can be written on devices:

OPC UA Type GreyCat mapping
Boolean bool
SByte int
Byte int
Int16 int
UInt16 int
Int32 int
UInt32 int
Int64 int
UInt64 int
Float float
Double float
String String
DateTime time
ByteString WRITING NOT SUPPORTED
XmlElement WRITING NOT SUPPORTED
Guid WRITING NOT SUPPORTED
StatusCode WRITING NOT SUPPORTED
NodeId WRITING NOT SUPPORTED
ExpandedNodeId WRITING NOT SUPPORTED
QualifiedName WRITING NOT SUPPORTED
LocalizedText WRITING NOT SUPPORTED
ExtensionObject WRITING NOT SUPPORTED
DataValue WRITING NOT SUPPORTED
Variant WRITING NOT SUPPORTED
DiagnosticInfo WRITING NOT SUPPORTED

Calling OPC-UA methods

The call method is used to invoke methods on OPC UA nodes. This method requires two parameters: the OPC UA node identifier and an array of parameters to pass to the method.

OpcuaCredentials

The OpcuaCredentials type represents credentials used for authentication in OPC UA connections.

  • login: String: Specifies the login or username associated with the credentials.
  • password: String: Specifies the password associated with the credentials.

OpcuaSecurityMode

The OpcuaSecurityMode is a GreyCat enum value representing the Security Mode to apply to the connection.

OpcuaSecurityMode::None

  • In this mode, no encryption or digital signatures are applied to the communication.
  • Data is transmitted in plaintext, making it vulnerable to interception and tampering.
  • This mode is suitable for environments where security is not a concern, such as local testing or isolated networks. However, it should not be used in production environments where data confidentiality and integrity are paramount.

OpcuaSecurityMode::Sign

  • In this mode, digital signatures are applied to the communication but encryption is not used.
  • Data remains in plaintext, but each message is signed with a cryptographic signature to ensure its integrity and authenticity.
  • While this mode provides some level of security by preventing tampering with data, it does not protect data confidentiality. It is suitable for scenarios where data confidentiality is not a requirement, but data integrity and authenticity are important.

OpcuaSecurityMode::SignAndEncrypt

  • This mode offers the highest level of security by both signing and encrypting communication.
  • Data is encrypted before transmission, ensuring confidentiality, and each message is signed to ensure its integrity and authenticity.
  • SignAndEncrypt mode provides comprehensive protection against eavesdropping, tampering, and unauthorized access, making it suitable for environments where data security is critical, such as industrial control systems and IIoT applications.

OpcuaSecurityPolicy

The OpcuaSecurityPolicy is a GreyCat enum value representing the Security Policy to apply to the connection.

OpcuaSecurityPolicy::Aes128_Sha256_RsaOaep

  • This security policy utilizes AES (Advanced Encryption Standard) with a key length of 128 bits for encryption, SHA-256 (Secure Hash Algorithm 256) for hashing, and RSA-OAEP (RSA Optimal Asymmetric Encryption Padding) for asymmetric encryption.
  • SHA-256 is a cryptographic hash function that produces a 256-bit (32-byte) hash value, ensuring data integrity.
  • RSA-OAEP is an asymmetric encryption scheme based on RSA, providing secure encryption and decryption of data.

OpcuaSecurityPolicy::Aes256_Sha256_RsaPss

  • This security policy is similar to Aes128_Sha256_RsaOaep but uses AES with a key length of 256 bits for encryption.
  • AES-256 offers stronger encryption compared to AES-128, suitable for environments requiring higher security levels.

OpcuaSecurityPolicy::Basic128Rsa15

  • This security policy utilizes Basic128 encryption and RSA with PKCS#1 v1.5 padding for asymmetric encryption.
  • Basic128 refers to AES with a key length of 128 bits for encryption, providing basic security measures.
  • RSA15 refers to RSA encryption with PKCS#1 v1.5 padding, an older encryption scheme that is less secure than RSA-OAEP.

OpcuaSecurityPolicy::Basic256

  • This security policy uses AES with a key length of 256 bits for encryption without specifying the hash and asymmetric encryption algorithms.
  • While it provides stronger encryption compared to Basic128, it lacks specificity regarding hashing and asymmetric encryption algorithms.

OpcuaSecurityPolicy::Basic256Sha256

  • Similar to Basic256, this security policy uses AES with a key length of 256 bits for encryption.
  • Additionally, it specifies SHA-256 for hashing, enhancing data integrity compared to Basic256.

OpcuaSecurityPolicy::None

  • This security policy indicates that no security measures are applied to communication.
  • It should only be used in environments where security is not a concern, such as local testing or isolated networks.

Security modes and Security policies compatibility

In OPC UA, not all combinations of security modes and security policies are compatible. Here’s a compatibility matrix indicating which security policies can be used with which security modes:

OpcuaSecurityPolicy / OpcuaSecurityMode None Sign SignAndEncrypt
None Y N N
Basic128Rsa15 N Y Y
Basic256 N Y Y
Basic256Sha256 N Y Y
Aes128_Sha256_RsaOaep N Y Y
Aes256_Sha256_RsaPss N Y Y
  • Y means the OpcuaSecurityPolicy is compatible with the OpcuaSecurityMode
  • N means the OpcuaSecurityPolicy is NOT compatible with the OpcuaSecurityMode

OpcuaCertificate

The OpcuaCertificate type represents a certificate used for OPC UA connections.

  • path: String: Specifies the file path to the certificate (containing the public key).
  • private_key_path: String: Specifies the file path to the private key associated with the certificate.
  • application_uri: String?: (Optional) Represents the application URI associated with the certificate.
  • allow_self_signed: bool?: (Optional) Indicates whether self-signed certificates are allowed. If true, self-signed certificates are permitted; if false or not specified, they are not allowed.

Depending on the selected OpcuaSecurityPolicy and OpcuaSecurityMode, the use of certificate can be mandatory:

OpcuaSecurityPolicy / OpcuaSecurityMode None Sign SignAndEncrypt
None N N N
Basic128Rsa15 N Y Y
Basic256 N Y Y
Basic256Sha256 N Y Y
Aes128_Sha256_RsaOaep N Y Y
Aes256_Sha256_RsaPss N Y Y
  • Y means a certificate must be provided to perfom the connection
  • N means no certificate is required to perform the connection

Examples

Read a single node

@library("opcua", "0.0.0");
use opcua;
fn main() {
    var opcua = OpcuaClient {
        host: "localhost",
        port: 4842,
        security_mode: OpcuaSecurityMode::SignAndEncrypt,
        security_policy: OpcuaSecurityPolicy::Aes128_Sha256_RsaOaep,
        certificate: OpcuaCertificate {
            allow_self_signed: true,
            path: "./test/key/client_cert.der",
            private_key_path: "./test/key/client_key.der",
        },
        credentials: OpcuaCredentials { login: "root", password: "opcua" }
    };
    var result = opcua.read("ns=0;i=2258");
    println(result);
    println(result is time);
}

This code perform a OPC UA connection to a local server running on port 4842. The connection is password protected and uses signature/encryption with the Aes128_Sha256_RsaOaep policy. We want to read the node i=2258 on namespace 0 (this node is present on all OPC UA servers and store the server timestamp). We also assess the result is a core::time.

Result:

'2024-05-30T09:49:38.074797+00:00'
true

Read a single node with times

In a similar way, we want to retrieve the content of node ns=0;i=2258, alongside with the server and source times:

@library("opcua", "0.0.0");
use opcua;
fn main() {
    var opcua = OpcuaClient {
        host: "localhost",
        port: 4842,
        security_mode: OpcuaSecurityMode::SignAndEncrypt,
        security_policy: OpcuaSecurityPolicy::Aes128_Sha256_RsaOaep,
        certificate: OpcuaCertificate {
            allow_self_signed: true,
            path: "./test/key/client_cert.der",
            private_key_path: "./test/key/client_key.der",
        },
        credentials: OpcuaCredentials { login: "root", password: "opcua" }
    };
    var result = opcua.read_with_time("ns=0;i=2258");
    println(result);
}

Result:

OpcuaValueDetails{value:'2024-05-30T09:51:59.498332+00:00',source_time:'2024-05-30T09:51:59Z',server_time:'2024-05-30T09:51:59Z'}

Writing a value

Node ns=1;s=boolean hosts a boolean value. We want to write a new value on this node:

@library("opcua", "0.0.0");
use opcua;
fn main() {
    var opcua = OpcuaClient {
        host: "localhost",
        port: 4842,
        security_mode: OpcuaSecurityMode::None,
        security_policy: OpcuaSecurityPolicy::None,
    };
    println(opcua.read("ns=1;s=boolean"));
    opcua.write("ns=1;s=boolean", true);
    println(opcua.read("ns=1;s=boolean"));
    opcua.write("ns=1;s=boolean", 42);
}

Result:

false //Original value
true //After writing
Error{code:ErrorCode::runtime_error,msg:"Unsupported GreyCat type (3) for OPCUA type: Boolean"}
    at main (project.gcl:21:38)

Initially, the value on the node was false. We replace it with true and a subsequent call shows the value has been correctly written back to the server. Writing an incompatible value (42 instead of a boolean value) raises a runtime_error.

Writing an array

Node ns=1;s=bool.array hosts a boolean array (size = 3). We want to write new values on this node:

@library("opcua", "0.0.0");
use opcua;
fn main() {
    var opcua = OpcuaClient {
        host: "localhost",
        port: 4842,
        security_mode: OpcuaSecurityMode::None,
        security_policy: OpcuaSecurityPolicy::None,
    };
    println(opcua.read("ns=1;s=bool.array"));
    opcua.write("ns=1;s=bool.array", [false, false, true]);
    println(opcua.read("ns=1;s=bool.array"));
}

Result:

[true,false,true] //Original value
[false,false,true] //After writing

Calling a method

Our OPC UA server exposes two RPC methods:

  • ns=1;i=62541: Take one string argument and return Hello <argument>
  • ns=1;s=IncInt32ArrayValues: Take two arguments, a int array, and an increment. This method returns an array with elements increased by the increment value
@library("opcua", "0.0.0");
use opcua;
use util; //For the Assert methods

fn main() {
    var opcua = OpcuaClient {
        host: "localhost",
        port: 4842,
        security_mode: OpcuaSecurityMode::SignAndEncrypt,
        security_policy: OpcuaSecurityPolicy::Aes128_Sha256_RsaOaep,
        certificate: OpcuaCertificate {
            allow_self_signed: true,
            path: "./test/key/client_cert.der",
            private_key_path: "./test/key/client_key.der",
        },
        credentials: OpcuaCredentials { login: "root", password: "opcua" }
    };
    Assert::equals("Hello DataThings", opcua.call("ns=1;i=62541", ["DataThings"]));
    Assert::equals("${[6, 7, 8, 9, 10]}", "${opcua.call("ns=1;s=IncInt32ArrayValues", [[1, 2, 3, 4, 5], 5])}");
}

Third-party licenses

open62541 (https://www.open62541.org/)

This library uses internally open62541, an Open Source OPC UA licensed under the MPL v2.0

openssl (https://www.openssl.org/)

open62541 is built with OpenSSL to perform the encryption. OpenSSL is licensed under Apache-2.0.