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.
| Stack | open62541 |
|---|---|
| Version | 1.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
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.
BadNoSubscriptionCall 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.
Other recipes in this stack
Concepts on this page
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.