C · recipe

Read historical data in C

open62541 exposes historical reads through UA_Client_HistoryRead_raw with a callback per batch. Bound the window, check the server claims a historical facet, and remember that history on a constrained device is usually better delegated upstream.

Stackopen62541
Version1.5.8

The code

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

static UA_Boolean onHistory(UA_Client *client, const UA_NodeId *nodeId,
                            UA_Boolean moreDataAvailable,
                            const UA_ExtensionObject *data, void *context) {
    (void)client; (void)nodeId; (void)context;
    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYDATA]) {
        UA_HistoryData *hd = (UA_HistoryData *)data->content.decoded.data;
        for (size_t i = 0; i < hd->dataValuesSize; i++) {
            UA_DataValue *dv = &hd->dataValues[i];
            if (dv->hasValue && dv->value.type == &UA_TYPES[UA_TYPES_DOUBLE])
                printf("  %.2f  status=%s\n", *(UA_Double *)dv->value.data,
                       UA_StatusCode_name(dv->status));
        }
    }
    printf("more data available: %d\n", moreDataAvailable);
    return true;   /* return false to stop paging */
}

int main(void) {
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    if (UA_Client_connect(client, "opc.tcp://milo.digitalpetri.com:62541/milo") != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client); return 1;
    }

    UA_DateTime end   = UA_DateTime_now();
    UA_DateTime start = end - (UA_DateTime)(10 * 60) * UA_DATETIME_SEC;

    UA_StatusCode rc = UA_Client_HistoryRead_raw(
        client, &UA_NODEID_STRING(2, "Dynamic/RandomDouble"),
        onHistory, start, end,
        UA_STRING_NULL, false, 5,          /* bound it: 5 values per node */
        UA_TIMESTAMPSTORETURN_BOTH, NULL);

    printf("history read: %s\n", UA_StatusCode_name(rc));

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

What it prints

  41.20  status=Good
  43.80  status=Good
  39.10  status=Good
  44.60  status=Good
  42.00  status=Good
more data available: 1
history read: Good

Notes

  • The callback is invoked per batch and its return value controls paging. Return false to stop rather than pulling an entire historian into a device with 64 MB of RAM.
  • Bound the request with numValuesPerNode and a short window, always.
  • Read ServerProfileArray first. If no historical facet is claimed, this will fail regardless of your code.
  • On embedded targets, consider whether history belongs here at all. Collecting live and letting an upstream historian own the archive is usually the right architecture.

What breaks first

The history read is refused by the server BadHistoryOperationUnsupported
The server does not implement historical access. Subscribe and forward instead, and let something upstream store it.
Other recipes in this stack

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