Discover ONVIF Devices Programmatically

A step-by-step guide using the Happytimesoft ONVIF Client Library API in C.

Introduction

This tutorial demonstrates how to use the Happytimesoft ONVIF Client Library to automatically discover ONVIF-compliant devices on your network. The discovery process uses the WS-Discovery protocol: the library sends probe packets (multicast), listens for device matches, and invokes a callback for each event. The code examples below are taken from the official onviftest.cpp test program.

Step 1: Initialize the Library and Set the Callback

Before starting discovery, initialize the library's internal buffers, set the log level, and register the callback that will be invoked when a device is discovered. Note that the callback also receives a msgtype parameter indicating which type of probe event occurred.

C Code: Device Discovery Setup (from onviftest)
// Open log file and set log level
log_init("onviftest.log");
log_set_level(HT_LOG_DBG);

// Initialize system buffer
sys_buf_init(10 * MAX_DEV_NUMS);

// Initialize HTTP message buffer
http_msg_buf_init(10 * MAX_DEV_NUMS);

// Register the probe callback
set_probe_cb(probeCallback, 0);

// Start the probe thread. Parameters:
//   - NULL  : discover on the default network interface
//   - 30    : probe interval in seconds
start_probe(NULL, 30);

Step 2: Implement the Probe Callback

The probe callback signature is void probeCallback(DEVICE_BINFO * p_res, int msgtype, void * p_data). It is called for three message types:

Message Type Meaning
PROBE_MSGTYPE_MATCH A device responded to the probe — a device is present on the network.
PROBE_MSGTYPE_HELLO A device announced itself on the network (Hello message).
PROBE_MSGTYPE_BYE A device announced it is leaving the network (Bye message).

Inside the callback you typically copy the device basic info, look for an existing device (deduplication by host/port or EndpointReference), and add new devices to your used list. For PROBE_MSGTYPE_BYE, mark the matching device as offline.

C Code: Probe Callback with Deduplication (from onviftest)
void probeCallback(DEVICE_BINFO * p_res, int msgtype, void * p_data)
{
    ONVIF_DEVICE_EX * p_dev = NULL;
    ONVIF_DEVICE_EX device;
    memset(&device, 0, sizeof(ONVIF_DEVICE_EX));

    if (msgtype == PROBE_MSGTYPE_MATCH || msgtype == PROBE_MSGTYPE_HELLO)
    {
        // Copy the discovered device basic info
        memcpy(&device.onvif_device.binfo, p_res, sizeof(DEVICE_BINFO));
        device.state = 1;

        // Avoid duplicates: search the used device list
        p_dev = findDevice(&device);
        if (p_dev == NULL)
        {
            printf("Found device. ip : %s, port : %d\n", p_res->XAddr.host, p_res->XAddr.port);
            p_dev = addDevice(&device);      // add to used list
            if (p_dev)
            {
                updateDevice(p_dev);          // fetch device info in background
            }
        }
        else
        {
            updateDevice(p_dev);
        }
    }
    else if (msgtype == PROBE_MSGTYPE_BYE)
    {
        // Find by EndpointReference and mark offline
        p_dev = findDeviceByEndpointReference(p_res->EndpointReference);
        if (p_dev)
        {
            p_dev->state = 0;
        }
    }
}

Step 3: Deduplicate Devices

Because the same device may be seen through MATCH and HELLO messages, always check whether the device already exists before adding it. The test program compares by host + port first, and falls back to comparing the EndpointReference when both are non-empty:

C Code: findDevice (from onviftest)
ONVIF_DEVICE_EX * findDevice(ONVIF_DEVICE_EX * pdevice)
{
    ONVIF_DEVICE_EX * p_dev = (ONVIF_DEVICE_EX *) pps_lookup_start(m_dev_ul);
    while (p_dev)
    {
        // Match by host + port
        if (strcmp(p_dev->onvif_device.binfo.XAddr.host, pdevice->onvif_device.binfo.XAddr.host) == 0 &&
            p_dev->onvif_device.binfo.XAddr.port == pdevice->onvif_device.binfo.XAddr.port)
        {
            break;
        }
        // Fallback: match by EndpointReference
        else if (p_dev->onvif_device.binfo.EndpointReference[0] != '\0' &&
                 pdevice->onvif_device.binfo.EndpointReference[0] != '\0' &&
                 !strcmp(p_dev->onvif_device.binfo.EndpointReference, pdevice->onvif_device.binfo.EndpointReference))
        {
            break;
        }
        p_dev = (ONVIF_DEVICE_EX *) pps_lookup_next(m_dev_ul, p_dev);
    }
    pps_lookup_end(m_dev_ul);
    return p_dev;
}

Step 4: Configure the New Device

When a new device is added, the test program sets its login information, authentication method, and request timeout before storing it in the used device list:

C Code: addDevice (from onviftest)
ONVIF_DEVICE_EX * addDevice(ONVIF_DEVICE_EX * pdevice)
{
    ONVIF_DEVICE_EX * p_dev = (ONVIF_DEVICE_EX *) pps_fl_pop(m_dev_fl);
    if (p_dev)
    {
        memcpy(p_dev, pdevice, sizeof(ONVIF_DEVICE_EX));
        p_dev->p_user = 0;
        p_dev->onvif_device.events.init_term_time = 60;

        // Set device login information (username / password)
        onvif_SetAuthInfo(&p_dev->onvif_device, "admin", "admin");

        // Set the authentication method (WS UsernameToken)
        onvif_SetAuthMethod(&p_dev->onvif_device, AuthMethod_UsernameToken);

        // Set the request timeout (5000 ms)
        onvif_SetReqTimeout(&p_dev->onvif_device, 5000);

        pps_ul_add(m_dev_ul, p_dev);
    }
    return p_dev;
}

Step 5: Fetch Device Information in the Background

After a device is discovered and added, a background thread (getDevInfoThread) retrieves its details: system date/time, endpoint reference, capabilities, services, device information, video sources, profiles, and stream URIs. The updateDevice() helper starts this thread when needed (e.g., a device has no current profile or stream URI yet).

C Code: updateDevice → getDevInfoThread (from onviftest)
void updateDevice(ONVIF_DEVICE_EX * p_device)
{
    if (NULL == p_device || p_device->thread_handler)
    {
        return;   // already being updated
    }

    BOOL need_update = FALSE;
    if (p_device->need_update) { need_update = TRUE; }

    if (!p_device->onvif_device.authFailed)
    {
        if (NULL == p_device->onvif_device.curProfile ||
            p_device->onvif_device.curProfile->stream_uri[0] == '\0')
        {
            need_update = TRUE;
        }
    }

    if (need_update)
    {
        p_device->need_update = 0;
        // Start a background thread to fetch device info and stream URIs
        p_device->thread_handler = sys_os_create_thread((void *)getDevInfoThread, p_device);
        if (p_device->thread_handler)
        {
            sys_os_detach_thread(p_device->thread_handler);
        }
    }
}

Step 6: Monitor Device State

Optionally, start a state detect thread that periodically polls every known device (via GetSystemDateAndTime) and updates its online/offline state. When a device transitions back online, it triggers an update so its information is re-fetched.

C Code: State Detection (from onviftest)
void startStateDetect()
{
    m_bStateDetect = TRUE;
    m_hStateDetect = sys_os_create_thread((void *)stateDetectThread, NULL);
}

// Inside stateDetectThread, every 30 seconds:
for (int i = 0; i < deviceNums; i++)
{
    ONVIF_DEVICE_EX * p_device = pps_get_node_by_index(m_dev_ul, i);
    if (p_device)
    {
        GetSystemDateAndTime(&p_device->onvif_device);
        if (p_device->onvif_device.errCode == ONVIF_ERR_ConnFailure)
            p_device->state = 0;   // offline
        else
            p_device->state = 1;   // online

        if (p_device->state) { updateDevice(p_device); }
    }
}

Key Notes

  • Passing NULL as the first parameter to start_probe uses the default network interface. You can pass a specific IP address to bind discovery to a particular interface.
  • The probe interval of 30 seconds is a common setting and can be adjusted based on your needs.
  • Always deduplicate devices (by host/port or EndpointReference) inside the callback, because the same device may be reported multiple times through MATCH and HELLO messages.
  • Handle PROBE_MSGTYPE_BYE to mark devices offline when they leave the network.
  • Set authentication info (onvif_SetAuthInfo), auth method, and request timeout before issuing ONVIF requests to the discovered device.
  • Run device-info fetching in a background thread so the main loop is not blocked.