Skip to content
Start free evaluation

C client

  • Open a secure channel and a session with the C ABI and activate it anonymously.
  • Send Read and Write requests encoded in UA Binary and decode their responses.
  • Browse the address space and call the Simulator.Add method of the PDComm simulator.
  • A lab folder that completed the lab setup: the native and apps packages, the environment variables, certificates in $PKI and built examples.
  • Two terminals, each with the environment variables set.

In the first terminal, start the SDK example server:

터미널 창
cd "$OPCUA_SDK"
build/opcua_server 127.0.0.1:4840 opc.tcp://localhost:4840/pdcomm "$PKI/server.der" "$PKI/server.key.pem" "$PKI/client.der"
Listening on 127.0.0.1:4840

Leave this terminal running until step 5.

opcua_client.c shows the whole flow from connecting to closing:

examples/opcua/opcua_client.c
/* PDComm OPC UA C client example.
Discovers the server endpoints, opens a secure session, optionally writes an
Int32 value and reads it back. Exit codes: 0 success, 1 a Bad OPC UA status,
2 invalid arguments, 3 SDK/runtime failure. Credentials are never printed. */
#ifndef _WIN32
#define _POSIX_C_SOURCE 200809L
#endif
#include <pdcomm/opcua/pdcomm_opcua.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "example_io.h"
#include "ua_binary.h"
static const char *USAGE =
"Usage: opcua_client ENDPOINT CLIENT_CERT_DER CLIENT_KEY_PEM TRUSTED_SERVER_DER [options]\n"
" --policy aes256sha256rsapss|aes128sha256rsaoaep|basic256sha256|none\n"
" --mode signandencrypt|sign|none (none requires --policy none)\n"
" --node ID numeric identifier in namespace 2 (default 1001)\n"
" --write VALUE write an Int32 value before reading it\n"
" --timeout MS network timeout in milliseconds (default 5000)\n";
static void print_error(const char *operation, int32_t status) {
uint8_t text[512];
size_t required = 0;
if (pdcomm_opcua_last_error_copy(text, sizeof(text) - 1, &required) != PDCOMM_OPCUA_OK)
required = 0;
text[required < sizeof(text) ? required : sizeof(text) - 1] = 0;
fprintf(stderr, "Error: %s failed (SDK status %d): %s\n", operation, (int)status,
required ? (const char *)text : "no detail");
}
static int parse_u32(const char *text, uint32_t minimum, uint32_t maximum, uint32_t *out) {
char *end = NULL;
unsigned long value = strtoul(text, &end, 10);
if (!*text || *end || value < minimum || value > maximum) return -1;
*out = (uint32_t)value;
return 0;
}
static void show_endpoint(ua_text url, uint32_t mode, ua_text policy, void *context) {
static const char *modes[] = {"Invalid", "None", "Sign", "SignAndEncrypt"};
(void)context;
printf("Endpoint %.*s mode=%s policy=%.*s\n", (int)url.size, url.data ? (const char *)url.data : "",
mode < 4 ? modes[mode] : "Unknown", (int)policy.size,
policy.data ? (const char *)policy.data : "");
}
/* Sends one request and polls until its response is available. */
static int32_t exchange(pdcomm_opcua_handle client, const uint8_t *request, size_t size,
uint32_t timeout_ms, uint8_t **body, size_t *body_size) {
uint32_t id = 0, waited = 0;
pdcomm_opcua_handle response = 0;
size_t required = 0;
int32_t status = pdcomm_opcua_client_begin(client, request, size, &id);
*body = NULL;
*body_size = 0;
if (status != PDCOMM_OPCUA_OK) return status;
for (;;) {
status = pdcomm_opcua_client_poll(client);
if (status != PDCOMM_OPCUA_OK) return status;
status = pdcomm_opcua_client_take_response(client, id, &response);
if (status == PDCOMM_OPCUA_OK) break;
if (status != PDCOMM_OPCUA_EMPTY) return status;
if (waited++ >= timeout_ms) return PDCOMM_OPCUA_RUNTIME_ERROR;
example_sleep_ms(1);
}
status = pdcomm_opcua_buffer_copy(response, NULL, 0, &required);
if (status == PDCOMM_OPCUA_BUFFER_TOO_SMALL && required > 0) {
*body = (uint8_t *)malloc(required);
status = *body ? pdcomm_opcua_buffer_copy(response, *body, required, body_size)
: PDCOMM_OPCUA_INTERNAL_ERROR;
}
pdcomm_opcua_release(response);
return status;
}
int main(int argc, char **argv) {
pdcomm_opcua_client_options options;
pdcomm_opcua_handle client = 0, endpoints = 0;
unsigned char *certificate = NULL, *key = NULL, *trust = NULL;
size_t certificate_size = 0, key_size = 0, trust_size = 0, size;
uint32_t policy = PDCOMM_OPCUA_AES256SHA256RSAPSS, mode = PDCOMM_OPCUA_SIGN_AND_ENCRYPT;
uint32_t node = 1001, timeout = 5000, service_result = 0, item_status = 0;
int32_t status, write_value = 0;
int write = 0, result = 3, i, mode_set = 0;
uint8_t request[256], *body = NULL;
ua_value value;
if (argc < 5 || (argc - 5) % 2) { fputs(USAGE, stderr); return 2; }
for (i = 5; i < argc; i += 2) {
const char *name = argv[i], *text = argv[i + 1];
if (!strcmp(name, "--policy")) {
if (!strcmp(text, "aes256sha256rsapss")) policy = PDCOMM_OPCUA_AES256SHA256RSAPSS;
else if (!strcmp(text, "aes128sha256rsaoaep")) policy = PDCOMM_OPCUA_AES128SHA256RSAOAEP;
else if (!strcmp(text, "basic256sha256")) policy = PDCOMM_OPCUA_BASIC256SHA256;
else if (!strcmp(text, "none")) policy = PDCOMM_OPCUA_POLICY_NONE;
else { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--mode")) {
mode_set = 1;
if (!strcmp(text, "signandencrypt")) mode = PDCOMM_OPCUA_SIGN_AND_ENCRYPT;
else if (!strcmp(text, "sign")) mode = PDCOMM_OPCUA_SIGN;
else if (!strcmp(text, "none")) mode = PDCOMM_OPCUA_MODE_NONE;
else { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--node")) {
if (parse_u32(text, 1, 0xffffffffu, &node)) { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--timeout")) {
if (parse_u32(text, 1, 600000, &timeout)) { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--write")) {
char *end = NULL;
long long parsed = strtoll(text, &end, 10);
if (!*text || *end || parsed < INT32_MIN || parsed > INT32_MAX) { fputs(USAGE, stderr); return 2; }
write = 1;
write_value = (int32_t)parsed;
} else { fputs(USAGE, stderr); return 2; }
}
if (policy == PDCOMM_OPCUA_POLICY_NONE && !mode_set) mode = PDCOMM_OPCUA_MODE_NONE;
if ((policy == PDCOMM_OPCUA_POLICY_NONE) != (mode == PDCOMM_OPCUA_MODE_NONE)) {
fputs("Error: --policy none and --mode none must be used together\n", stderr);
return 2;
}
if (pdcomm_opcua_abi_version() != 0x00000002u) {
fputs("Error: unsupported PDComm OPC UA ABI version\n", stderr);
return 3;
}
printf("PDComm OPC UA ABI 0x%08x\n", (unsigned)pdcomm_opcua_abi_version());
/* Discovery is unauthenticated and returns a GetEndpointsResponse. */
status = pdcomm_opcua_discover((const uint8_t *)argv[1], strlen(argv[1]), timeout, &endpoints);
if (status != PDCOMM_OPCUA_OK) { print_error("discover", status); return 3; }
status = pdcomm_opcua_buffer_copy(endpoints, NULL, 0, &size);
if (status == PDCOMM_OPCUA_BUFFER_TOO_SMALL && (body = (uint8_t *)malloc(size)) != NULL)
status = pdcomm_opcua_buffer_copy(endpoints, body, size, &size);
pdcomm_opcua_release(endpoints);
if (status != PDCOMM_OPCUA_OK || !body || ua_decode_endpoints(body, size, show_endpoint, NULL) < 0) {
free(body);
fputs("Error: invalid GetEndpoints response\n", stderr);
return 3;
}
free(body);
body = NULL;
if (example_load(argv[2], &certificate, &certificate_size) ||
example_load(argv[3], &key, &key_size) || example_load(argv[4], &trust, &trust_size)) {
fputs("Error: cannot read the certificate, private key or trusted server certificate\n", stderr);
goto done;
}
memset(&options, 0, sizeof(options));
options.struct_size = (uint32_t)sizeof(options);
options.version = 1;
status = pdcomm_opcua_client_options_init(&options);
if (status != PDCOMM_OPCUA_OK) { print_error("options", status); goto done; }
options.timeout_ms = timeout;
options.policy = policy;
options.mode = mode;
options.flags = policy == PDCOMM_OPCUA_POLICY_NONE ? PDCOMM_OPCUA_ALLOW_NONE : 0;
options.certificate_der.data = certificate;
options.certificate_der.size = certificate_size;
options.private_key_pem.data = key;
options.private_key_pem.size = key_size;
options.trusted_server_der.data = trust;
options.trusted_server_der.size = trust_size;
status = pdcomm_opcua_client_connect((const uint8_t *)argv[1], strlen(argv[1]), &options, &client);
/* The SDK copied the identity; wipe the private key now. */
example_wipe(key, key_size);
key = NULL;
if (status != PDCOMM_OPCUA_OK) { print_error("connect", status); goto done; }
status = pdcomm_opcua_client_activate_anonymous(client);
if (status != PDCOMM_OPCUA_OK) { print_error("activate", status); goto done; }
printf("Session activated (anonymous)\n");
result = 0;
if (write) {
size = ua_encode_write_int32(request, sizeof(request), 2, node, write_value);
status = exchange(client, request, size, timeout, &body, &size);
if (status != PDCOMM_OPCUA_OK) { print_error("write", status); result = 3; goto done; }
if (ua_decode_write(body, size, &service_result, &item_status)) {
fputs("Error: malformed WriteResponse\n", stderr);
result = 3;
goto done;
}
printf("Write ns=2;i=%u value=%d service=0x%08x status=0x%08x\n", (unsigned)node,
(int)write_value, (unsigned)service_result, (unsigned)item_status);
if ((service_result | item_status) & 0x80000000u) result = 1;
free(body);
body = NULL;
}
size = ua_encode_read(request, sizeof(request), 2, node);
status = exchange(client, request, size, timeout, &body, &size);
if (status != PDCOMM_OPCUA_OK) { print_error("read", status); result = 3; goto done; }
if (ua_decode_read(body, size, &service_result, &value)) {
fputs("Error: malformed ReadResponse\n", stderr);
result = 3;
goto done;
}
if (value.has_int32)
printf("Read ns=2;i=%u value=%d service=0x%08x status=0x%08x\n", (unsigned)node,
(int)value.int32, (unsigned)service_result, (unsigned)value.status);
else
printf("Read ns=2;i=%u value=none service=0x%08x status=0x%08x\n", (unsigned)node,
(unsigned)service_result, (unsigned)value.status);
if ((service_result | value.status) & 0x80000000u) result = 1;
status = pdcomm_opcua_client_close(client);
if (status != PDCOMM_OPCUA_OK) { print_error("close", status); result = 3; goto done; }
printf("Session closed\n");
done:
free(body);
if (client) pdcomm_opcua_release(client);
if (key) example_wipe(key, key_size);
free(certificate);
free(trust);
return result;
}

Step by step:

  1. Check the ABI. Make sure pdcomm_opcua_abi_version returns 0x00000002; if the header and the library disagree, stop.
  2. Discover. pdcomm_opcua_discover returns a GetEndpointsResponse buffer, which the example prints with ua_decode_endpoints. Discovery doesn’t validate certificates, so never trust a server based on this result.
  3. Set options. Fill in struct_size and version, get the defaults from pdcomm_opcua_client_options_init, then set the policy, mode, timeout, client certificate and private key, and the server certificate to trust.
  4. Connect. pdcomm_opcua_client_connect picks the endpoint, validates the server certificate and opens the secure channel and session. The SDK has copied the private key, so the example wipes its own buffer right away with example_wipe.
  5. Activate. pdcomm_opcua_client_activate_anonymous activates the session with an anonymous token.
  6. Request and response. The exchange function handles one request: it queues it with pdcomm_opcua_client_begin, then calls pdcomm_opcua_client_poll and pdcomm_opcua_client_take_response until the response arrives. It copies the response bytes by calling pdcomm_opcua_buffer_copy twice, first to ask for the size and then to copy, and releases the response handle with pdcomm_opcua_release.
  7. Decode. ua_decode_write and ua_decode_read extract the service result and the item result. If either is Bad, the program exits with code 1.
  8. Close. pdcomm_opcua_client_close sends CloseSession and CloseSecureChannel, and the client handle is released last.

ua_binary.h builds the request messages. ua_encode_read and ua_encode_write_int32 write the encoding NodeId of ReadRequest (i=631) or WriteRequest (i=673), a request header and the body. The authentication token, timestamp and request handle in the header stay empty; the SDK fills them in when it sends the request.

In the second terminal, write a value and read it back:

터미널 창
cd "$OPCUA_SDK"
build/opcua_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --write 7

After the endpoint list, the output is:

Session activated (anonymous)
Write ns=2;i=1001 value=7 service=0x00000000 status=0x00000000
Read ns=2;i=1001 value=7 service=0x00000000 status=0x00000000
Session closed

Try writing the read-only variable ns=2;i=1002:

터미널 창
build/opcua_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --node 1002 --write 1
Session activated (anonymous)
Write ns=2;i=1002 value=1 service=0x00000000 status=0x803b0000
Read ns=2;i=1002 value=100 service=0x00000000 status=0x00000000
Session closed

The service result is Good; only the item result is 0x803b0000 (BadNotWritable). The value stays 100 and the exit code is 1.

An anonymous session reading ns=2;i=1003, which is for authenticated users only, gets 0x801f0000 (BadUserAccessDenied):

터미널 창
build/opcua_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --node 1003
Session activated (anonymous)
Read ns=2;i=1003 value=none service=0x00000000 status=0x801f0000
Session closed

Use --policy and --mode to change the security policy and mode:

터미널 창
build/opcua_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --policy basic256sha256 --mode sign
Session activated (anonymous)
Read ns=2;i=1001 value=7 service=0x00000000 status=0x00000000
Session closed
Option Description
--policy aes256sha256rsapss, aes128sha256rsaoaep, basic256sha256, none Security policy (default Aes256_Sha256_RsaPss)
--mode signandencrypt, sign, none Security mode (default SignAndEncrypt); use none together with --policy none
--node ID Numeric identifier in namespace 2 (default 1001)
--write VALUE Writes an Int32 value before reading
--timeout MS Network timeout (default 5000 ms)

Exit codes: 0 success, 1 a Bad StatusCode was received, 2 invalid arguments, 3 a connection or SDK failure.

opcua_browse_call.c opens the session the same way, then sends Browse and Call:

examples/opcua/opcua_browse_call.c
/* PDComm OPC UA C browse and call example.
Opens a secure anonymous session, lists the forward hierarchical references
of a node (Browse) and optionally calls a method with Double arguments
(Call). Exit codes: 0 success, 1 a Bad OPC UA status, 2 invalid arguments,
3 SDK/runtime failure. Credentials are never printed. */
#ifndef _WIN32
#define _POSIX_C_SOURCE 200809L
#endif
#include <pdcomm/opcua/pdcomm_opcua.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "example_io.h"
#include "ua_binary.h"
#define MAX_ARGUMENTS 8u
static const char *USAGE =
"Usage: opcua_browse_call ENDPOINT CLIENT_CERT_DER CLIENT_KEY_PEM TRUSTED_SERVER_DER [options]\n"
" --browse NODEID node whose references are listed (default i=85, the Objects folder)\n"
" --method NODEID method to call after browsing, for example ns=2;s=Simulator.Add\n"
" --object NODEID object that owns the method (default ns=2;s=Simulator)\n"
" --arg VALUE Double input argument; repeat it for each argument (at most 8)\n"
" --policy aes256sha256rsapss|aes128sha256rsaoaep|basic256sha256\n"
" --mode signandencrypt|sign\n"
" --timeout MS network timeout in milliseconds (default 5000)\n";
static const char *node_class(uint32_t value) {
switch (value) {
case 1: return "Object";
case 2: return "Variable";
case 4: return "Method";
case 8: return "ObjectType";
case 16: return "VariableType";
case 32: return "ReferenceType";
case 64: return "DataType";
case 128: return "View";
default: return "Unspecified";
}
}
/* Writes "ns=2;s=Name", "i=85" or "ns=1;(opaque)" for printing. */
static void node_text(const ua_node *node, char *out, size_t capacity) {
char prefix[16] = "";
if (node->ns) snprintf(prefix, sizeof(prefix), "ns=%u;", (unsigned)node->ns);
if (node->kind == UA_NODE_STRING)
snprintf(out, capacity, "%ss=%.*s", prefix, (int)(node->string.size > 200 ? 200 : node->string.size),
node->string.data ? (const char *)node->string.data : "");
else if (node->kind == UA_NODE_NUMERIC)
snprintf(out, capacity, "%si=%lu", prefix, (unsigned long)node->numeric);
else
snprintf(out, capacity, "%s(opaque)", prefix);
}
static void show_reference(const ua_reference *reference, void *context) {
char target[256];
(void)context;
node_text(&reference->node, target, sizeof(target));
printf(" %-8s %s%s \"%.*s\"\n", node_class(reference->node_class), target, reference->remote ? " (remote)" : "",
(int)(reference->browse_name.size > 200 ? 200 : reference->browse_name.size),
reference->browse_name.data ? (const char *)reference->browse_name.data : "");
}
static void print_error(const char *operation, int32_t status) {
uint8_t text[512];
size_t required = 0;
if (pdcomm_opcua_last_error_copy(text, sizeof(text) - 1, &required) != PDCOMM_OPCUA_OK)
required = 0;
text[required < sizeof(text) ? required : sizeof(text) - 1] = 0;
fprintf(stderr, "Error: %s failed (SDK status %d): %s\n", operation, (int)status,
required ? (const char *)text : "no detail");
}
/* Sends one request and polls until its response is available. */
static int32_t exchange(pdcomm_opcua_handle client, const uint8_t *request, size_t size,
uint32_t timeout_ms, uint8_t **body, size_t *body_size) {
uint32_t id = 0, waited = 0;
pdcomm_opcua_handle response = 0;
size_t required = 0;
int32_t status = pdcomm_opcua_client_begin(client, request, size, &id);
*body = NULL;
*body_size = 0;
if (status != PDCOMM_OPCUA_OK) return status;
for (;;) {
status = pdcomm_opcua_client_poll(client);
if (status != PDCOMM_OPCUA_OK) return status;
status = pdcomm_opcua_client_take_response(client, id, &response);
if (status == PDCOMM_OPCUA_OK) break;
if (status != PDCOMM_OPCUA_EMPTY) return status;
if (waited++ >= timeout_ms) return PDCOMM_OPCUA_RUNTIME_ERROR;
example_sleep_ms(1);
}
status = pdcomm_opcua_buffer_copy(response, NULL, 0, &required);
if (status == PDCOMM_OPCUA_BUFFER_TOO_SMALL && required > 0) {
*body = (uint8_t *)malloc(required);
status = *body ? pdcomm_opcua_buffer_copy(response, *body, required, body_size)
: PDCOMM_OPCUA_INTERNAL_ERROR;
}
pdcomm_opcua_release(response);
return status;
}
int main(int argc, char **argv) {
pdcomm_opcua_client_options options;
pdcomm_opcua_handle client = 0;
unsigned char *certificate = NULL, *key = NULL, *trust = NULL;
size_t certificate_size = 0, key_size = 0, trust_size = 0, size = 0;
uint32_t policy = PDCOMM_OPCUA_AES256SHA256RSAPSS, mode = PDCOMM_OPCUA_SIGN_AND_ENCRYPT, timeout = 5000;
uint32_t service_result = 0, status_code = 0, inputs[MAX_ARGUMENTS], input_count = 0, output_count = 0, i;
uint32_t argument_count = 0;
double arguments[MAX_ARGUMENTS];
ua_number outputs[MAX_ARGUMENTS];
const char *browse_text = "i=85", *object_text = "ns=2;s=Simulator", *method_text = NULL;
ua_node browse_node, object_node, method_node;
uint8_t request[8192], *body = NULL;
int32_t status;
int result = 3, more = 0, a;
if (argc < 5 || (argc - 5) % 2) { fputs(USAGE, stderr); return 2; }
for (a = 5; a < argc; a += 2) {
const char *name = argv[a], *text = argv[a + 1];
char *end = NULL;
if (!strcmp(name, "--browse")) browse_text = text;
else if (!strcmp(name, "--object")) object_text = text;
else if (!strcmp(name, "--method")) method_text = text;
else if (!strcmp(name, "--arg")) {
if (argument_count == MAX_ARGUMENTS) { fputs(USAGE, stderr); return 2; }
arguments[argument_count] = strtod(text, &end);
if (!*text || *end) { fputs(USAGE, stderr); return 2; }
++argument_count;
} else if (!strcmp(name, "--policy")) {
if (!strcmp(text, "aes256sha256rsapss")) policy = PDCOMM_OPCUA_AES256SHA256RSAPSS;
else if (!strcmp(text, "aes128sha256rsaoaep")) policy = PDCOMM_OPCUA_AES128SHA256RSAOAEP;
else if (!strcmp(text, "basic256sha256")) policy = PDCOMM_OPCUA_BASIC256SHA256;
else { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--mode")) {
if (!strcmp(text, "signandencrypt")) mode = PDCOMM_OPCUA_SIGN_AND_ENCRYPT;
else if (!strcmp(text, "sign")) mode = PDCOMM_OPCUA_SIGN;
else { fputs(USAGE, stderr); return 2; }
} else if (!strcmp(name, "--timeout")) {
unsigned long value = strtoul(text, &end, 10);
if (!*text || *end || value < 1 || value > 600000) { fputs(USAGE, stderr); return 2; }
timeout = (uint32_t)value;
} else { fputs(USAGE, stderr); return 2; }
}
if (ua_parse_node(browse_text, &browse_node) || ua_parse_node(object_text, &object_node) ||
(method_text && ua_parse_node(method_text, &method_node))) {
fputs("Error: a NodeId must look like i=85, ns=2;i=1001 or ns=2;s=Name\n", stderr);
return 2;
}
if (pdcomm_opcua_abi_version() != 0x00000002u) {
fputs("Error: unsupported PDComm OPC UA ABI version\n", stderr);
return 3;
}
if (example_load(argv[2], &certificate, &certificate_size) ||
example_load(argv[3], &key, &key_size) || example_load(argv[4], &trust, &trust_size)) {
fputs("Error: cannot read the certificate, private key or trusted server certificate\n", stderr);
goto done;
}
memset(&options, 0, sizeof(options));
options.struct_size = (uint32_t)sizeof(options);
options.version = 1;
status = pdcomm_opcua_client_options_init(&options);
if (status != PDCOMM_OPCUA_OK) { print_error("options", status); goto done; }
options.timeout_ms = timeout;
options.policy = policy;
options.mode = mode;
options.certificate_der.data = certificate;
options.certificate_der.size = certificate_size;
options.private_key_pem.data = key;
options.private_key_pem.size = key_size;
options.trusted_server_der.data = trust;
options.trusted_server_der.size = trust_size;
status = pdcomm_opcua_client_connect((const uint8_t *)argv[1], strlen(argv[1]), &options, &client);
/* The SDK copied the identity; wipe the private key now. */
example_wipe(key, key_size);
key = NULL;
if (status != PDCOMM_OPCUA_OK) { print_error("connect", status); goto done; }
status = pdcomm_opcua_client_activate_anonymous(client);
if (status != PDCOMM_OPCUA_OK) { print_error("activate", status); goto done; }
printf("Session activated (anonymous)\n");
/* Browse: forward hierarchical references, at most 100 per call. */
result = 0;
size = ua_encode_browse(request, sizeof(request), &browse_node, 100);
status = size ? exchange(client, request, size, timeout, &body, &size) : PDCOMM_OPCUA_INVALID_INPUT;
if (status != PDCOMM_OPCUA_OK) { print_error("browse", status); result = 3; goto done; }
printf("Browse %s\n", browse_text);
if (ua_decode_browse(body, size, &service_result, &status_code, &more, show_reference, NULL)) {
fputs("Error: malformed BrowseResponse\n", stderr);
result = 3;
goto done;
}
printf("Browse service=0x%08x status=0x%08x%s\n", (unsigned)service_result, (unsigned)status_code,
more ? " (more references: continue with BrowseNext)" : "");
if ((service_result | status_code) & 0x80000000u) result = 1;
free(body);
body = NULL;
/* Call: one method with the Double input arguments from the command line. */
if (method_text) {
size = ua_encode_call(request, sizeof(request), &object_node, &method_node, arguments, argument_count);
status = size ? exchange(client, request, size, timeout, &body, &size) : PDCOMM_OPCUA_INVALID_INPUT;
if (status != PDCOMM_OPCUA_OK) { print_error("call", status); result = 3; goto done; }
if (ua_decode_call(body, size, &service_result, &status_code, inputs, &input_count, outputs, &output_count,
MAX_ARGUMENTS)) {
fputs("Error: malformed CallResponse or an output type this example does not decode\n", stderr);
result = 3;
goto done;
}
printf("Call %s service=0x%08x status=0x%08x", method_text, (unsigned)service_result, (unsigned)status_code);
for (i = 0; i < input_count; ++i)
if (inputs[i] & 0x80000000u) printf(" input%u=0x%08x", (unsigned)i + 1, (unsigned)inputs[i]);
for (i = 0; i < output_count; ++i) printf(" output%u=%.17g", (unsigned)i + 1, outputs[i].value);
printf("\n");
if ((service_result | status_code) & 0x80000000u) result = 1;
}
status = pdcomm_opcua_client_close(client);
if (status != PDCOMM_OPCUA_OK) { print_error("close", status); result = 3; goto done; }
printf("Session closed\n");
done:
free(body);
if (client) pdcomm_opcua_release(client);
if (key) example_wipe(key, key_size);
free(certificate);
free(trust);
return result;
}
  • ua_parse_node reads NodeId text such as i=85, ns=2;i=1001 or ns=2;s=Simulator.Add.
  • ua_encode_browse builds a BrowseRequest (i=527) for forward HierarchicalReferences (i=33) and their subtypes, every NodeClass and every result field, with up to 100 references per node.
  • ua_decode_browse calls show_reference for each reference. The columns are the NodeClass, the target NodeId and the BrowseName. If references remain, the program tells you to continue with BrowseNext.
  • ua_encode_call builds a CallRequest (i=712) with Double Variant input arguments, and ua_decode_call extracts the method status, the per-argument results and the output arguments.

In the second terminal, browse the Objects folder (i=85):

터미널 창
build/opcua_browse_call opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der"
Session activated (anonymous)
Browse i=85
Object i=2253 "Server"
Variable ns=2;i=1001 "Value"
Variable ns=2;i=1002 "Limit"
Variable ns=2;i=1003 "Protected"
Browse service=0x00000000 status=0x00000000
Session closed

Browse below the Server object too:

터미널 창
build/opcua_browse_call opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --browse i=2253
Session activated (anonymous)
Browse i=2253
Object i=2268 "ServerCapabilities"
Object i=2295 "VendorServerInfo"
Object i=2296 "ServerRedundancy"
Variable i=2256 "ServerStatus"
Variable i=2254 "ServerArray"
Variable i=2255 "NamespaceArray"
Variable i=2267 "ServiceLevel"
Variable i=2994 "Auditing"
Object i=2274 "ServerDiagnostics"
Browse service=0x00000000 status=0x00000000
Session closed

The example server has no methods. Press Ctrl+C in the first terminal to stop it.

6. Call a method on the simulator’s server

Section titled “6. Call a method on the simulator’s server”

The PDComm simulator’s server has a Simulator.Add method. In the first terminal, run the headless simulator from the $PKI folder:

터미널 창
cd "$PKI"
"$OPCUA_APPS/bin/pdcomm-opcua-headless"

The program reads one JSON command per line from standard input. Type this line to set the server’s address and certificates; file names are relative to the current folder:

{"action":"configure","config":{"server":{"address":"127.0.0.1:4841","certificate":"server.cert.pem","private_key":"server.key.pem","trust":["client.der"]}}}

The reply is the whole configuration on one line. Then start the server:

{"action":"server_start"}
{"ok":true,"result":{"audit":[],"endpoint":"opc.tcp://localhost:4841/pdcomm/simulator","error":null,"running":true,"secure":true,"sessions":0}}

In the second terminal, browse the Simulator object and call Simulator.Add with 1.5 and 2. Quote NodeIds that contain ;:

터미널 창
build/opcua_browse_call opc.tcp://localhost:4841/pdcomm/simulator "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" \
--browse 'ns=2;s=Simulator' --method 'ns=2;s=Simulator.Add' --arg 1.5 --arg 2
Session activated (anonymous)
Browse ns=2;s=Simulator
Variable ns=2;s=Simulator.Boolean "Simulator.Boolean"
Variable ns=2;s=Simulator.Int32 "Simulator.Int32"
Variable ns=2;s=Simulator.Double "Simulator.Double"
Variable ns=2;s=Simulator.String "Simulator.String"
Variable ns=2;s=Simulator.Counter "Simulator.Counter"
Variable ns=2;s=Simulator.Protected "Simulator.Protected"
Variable ns=2;s=Simulator.AlarmSource "Simulator.AlarmSource"
Method ns=2;s=Simulator.Add "Simulator.Add"
Method ns=2;s=Simulator.Delay "Simulator.Delay"
Browse service=0x00000000 status=0x00000000
Call ns=2;s=Simulator.Add service=0x00000000 status=0x00000000 output1=3.5
Session closed

Without --object, the method’s object is ns=2;s=Simulator. With only one argument, the method returns 0x80760000 (BadArgumentsMissing) and the exit code is 1:

터미널 창
build/opcua_browse_call opc.tcp://localhost:4841/pdcomm/simulator "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" \
--method 'ns=2;s=Simulator.Add' --arg 1.5
Session activated (anonymous)
Browse i=85
Object i=2253 "Server"
Object ns=2;s=Simulator "Simulator"
Browse service=0x00000000 status=0x00000000
Call ns=2;s=Simulator.Add service=0x00000000 status=0x80760000
Session closed

With three arguments, you get 0x80e50000 (BadTooManyArguments).

Type this line in the headless simulator’s terminal. It stops the server and the program exits.

{"action":"shutdown"}
{"ok":true,"result":{"client_closed":null,"pubsub_stopped":false,"server_stopped":true}}
Symptom Cause and fix
Error: a NodeId must look like i=85, ns=2;i=1001 or ns=2;s=Name The shell split the command at ;. Quote the NodeId, as in 'ns=2;s=Simulator'.
Error: discover failed (SDK status 5): OPC UA status: 0x80830000 The URL path doesn’t match the server (BadTcpEndpointUrlInvalid). The simulator’s path is /pdcomm/simulator.
Error: connect failed (SDK status 5): Requested OPC UA endpoint not advertised The server doesn’t advertise an endpoint with the requested policy and mode. The example server doesn’t advertise None, so --policy none is refused.
Error: connect failed (SDK status 5): OPC UA security CertificateUntrusted The server certificate you trust isn’t the server’s actual certificate. Check PKI and trust setup.