C · recipe

Subscribe to data changes in C

Create a subscription, add a monitored item with a callback, then drive the client's event loop so notifications get processed. That last step is the one people miss: without iterating the client, nothing arrives.

Stackopen62541
Version1.5.8

The code

#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/client_subscriptions.h>
#include <stdio.h>
#include <signal.h>

static volatile UA_Boolean running = true;
static void stop(int sig) { (void)sig; running = false; }

static void onChange(UA_Client *client, UA_UInt32 subId, void *subContext,
                     UA_UInt32 monId, void *monContext, UA_DataValue *value) {
    (void)client; (void)subId; (void)subContext; (void)monId; (void)monContext;
    if (value->hasValue && value->value.type == &UA_TYPES[UA_TYPES_DATETIME]) {
        UA_DateTimeStruct ts = UA_DateTime_toStruct(*(UA_DateTime *)value->value.data);
        printf("%02u:%02u:%02u status=%s\n", ts.hour, ts.min, ts.sec,
               UA_StatusCode_name(value->status));
    }
}

int main(void) {
    signal(SIGINT, stop);

    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    if (UA_Client_connect(client, "opc.tcp://opcua.umati.app:4840") != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client); return 1;
    }

    UA_CreateSubscriptionRequest sreq = UA_CreateSubscriptionRequest_default();
    sreq.requestedPublishingInterval = 1000.0;   /* what the consumer needs */
    UA_CreateSubscriptionResponse sres =
        UA_Client_Subscriptions_create(client, sreq, NULL, NULL, NULL);
    if (sres.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client); return 1;
    }
    printf("revised publishing interval: %.0f ms\n", sres.revisedPublishingInterval);

    UA_MonitoredItemCreateRequest mreq =
        UA_MonitoredItemCreateRequest_default(
            UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
    mreq.requestedParameters.samplingInterval = 250.0;
    mreq.requestedParameters.queueSize = 10;
    mreq.requestedParameters.discardOldest = true;

    UA_Client_MonitoredItems_createDataChange(
        client, sres.subscriptionId, UA_TIMESTAMPSTORETURN_BOTH,
        mreq, NULL, onChange, NULL);

    /* you MUST iterate the client or nothing is delivered */
    while (running)
        UA_Client_run_iterate(client, 100);

    UA_Client_disconnect(client);
    UA_Client_delete(client);
    return 0;
}

What it prints

revised publishing interval: 1000 ms
03:45:00 status=Good
03:45:01 status=Good
03:45:02 status=Good

Notes

  • UA_Client_run_iterate is what processes incoming publishes. Forgetting it is the single most common reason a C subscription appears to do nothing.
  • Read revisedPublishingInterval from the response. You requested a rate; the server decided one.
  • requestedPublishingInterval is delivery; samplingInterval on the item is how often the server looks. They are different numbers and confusing them is the usual subscription bug.
  • The callback runs on the iterate thread. Do not block in it: queue the work.
  • Size queueSize from the publishing-to-sampling ratio, and remember an overflow still reports Good.

What breaks first

The subscription is created but the callback never fires BadNoSubscription
Call UA_Client_run_iterate in a loop. Without it the client never processes the server's publish responses, and the subscription eventually times out server-side.

Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.

Free download
OPC UA Troubleshooting & Integration Checklist
The field-tested checklist engineers use to diagnose certificates, ports and namespace problems fast, plus a Python (asyncua) client template.
Get the free checklist →
Free, no course, no login