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.
// 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.
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:
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:
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).
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.
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
NULLas the first parameter tostart_probeuses 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_BYEto 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.