ANSI C Based OPC UA Client/Server/PubSub SDK
1.9.2.463
|
Most examples in the SDK are using the OpcUa_Timer
functionality of the UaStack to implement sampling.
This however is not necessary. E.g. a data provider can work completely event based without polling or it can use a better timer implementation. A problem when using OpcUa_Timer is that it is not very accurate and no fast sampling rates are possible due to the fact that it is derived from the Berkeley socket select timeout.
Thus, we use POSIX timers in this example which have a resolution of nanoseconds. This example was tested to work on Linux, QNX and Windows.
Files used in this lesson:
In this example we create a provider which allows to sample data at 1 Khz (1 ms sampling interval) using POSIX timers. To reduce network load we do not publish the values at the same rate, but allow to queue the sampled data in MonitoredItem queues and publish information at a lower publishing frequency without losing any data. This is called oversampling in OPC UA, hence the title of this example.
A client could e.g. configure these settings:
The following picture from the chapter OPC UA Client/Server Subscription Concept shows an overview of the OPC UA sampling mechanism. You should read this chapter before continuing with this example.
Note that sampling of the data in this example is done in an own thread. Thus, this example requires threading. The OPC UA SDK itself works single-threaded, but it is possible to do sampling of underlying data in a separate thread and call UaServer_NewItemValue from this thread. This requires UaServer_NewItemValue to be thread-safe which is the case if the SDK is compiled with SYNCHRONIZATION
support enabled.
Creating a POSIX timer consists of the following steps:
This can be done by using the two functions timer_create
and timer_settime
.
The function timer_create can be used to create a timer.
The clockid
argument specifies the clock that the new timer uses to measure time. It can be chosen from one of the following values:
The parameter sevp
configures how the caller should be notified. There are basically four options:
On success, timer_create
returns 0, and the ID of the new timer is placed in *timerid
.
The function timer_settime arms or disarms the timer identified by timerid
. The new_value
argument is pointer to an itimerspec
structure that specifies the new initial value and the new interval for the timer. The itimerspec
structure is defined as follows:
Each of the substructures of the itimerspec
structure is a timespec
structure that allows a time value to be specified in seconds and nanoseconds. These time values are measured according to the clock that was specified when the timer was created by timer_create(2)
If new_value->it_value
specifies a nonzero value (i.e. either subfield is nonzero), then timer_settime()
arms (starts) the timer, setting it to initially expire at the given time. (If the timer was already armed, then the previous settings are overwritten.) If new_value->it_value specifies a zero value (i.e. both subfields are zero), then the timer is disarmed.
The new_value->it_interval
field specifies the period of the timer, in seconds and nanoseconds. If this field is nonzero, then each time that an armed timer expires, the timer is reloaded from the value specified in new_value->it_interval
. If new_value->it_interval
specifies a zero value, then the timer expires just once: at the time specified by it_value
.
Polling (in this case simulating) the underlying data is done in an own thread. We want to wake this thread up periodically when the timer expires and let it sleep otherwise. For the notification mechanism SIGEV_THREAD sounds reasonable, but this would create a new thread for every notification. We don’t want the overhead of creating and deleting threads every millisecond so we go for the option SIGEV_SIGNAL which notifies our process using signals which are available on all UNIX like systems.
To wake up our thread using signals we first need to block the signal using sigprocmask
, so no signal handler needs to be implemented. Instead we call the function sigwaitinfo
in our thread to suspend execution until the timer expires. If the specified signal is pending, this function returns and we do our sampling.
As signal number we use SIGALRM and create our own define for it so that it is easily possible to change the used signal number when necessary.
Our sample provider creates a list of sampling rates in almost the same way as in all other examples. The difference is that the fastest rate is now 1 ms. In UaProvider_OverSampling_Subscription_Initialize()
we create the POSIX timer thread and the POSIX timers necessary for sampling:
The timer_thread
blocks on the sigwaitinfo
call and calls UaProvider_OverSampling_SampleData()
when a samplig timer has expired.
The POSIX timer API allows to configure timers at a resolution of 1 nanosecond. The accuracy of the timer depends on the operating system used and on the underlying hardware.
timespec
datatype used by POSIX APIs:
On older Linux systems the accuracy of system calls that set timeouts was limited by the resolution of the software clock, a clock maintained by the kernel which measures time in jiffies
. The size of a jiffy
is determined by the value of the kernel constant HZ
. In kernel 2.4.x HZ
was 100, giving a jiffy
value of 0.01 seconds. Since kernel 2.6.13 this was configurable and could be 100, 250, or 1000 Hz.
Since kernel 2.6.21 Linux supports high-resolution timers (HRTs). On a system that supports HRTs, the accuracy of sleep and timer system calls is no longer constrained by the jiffy, but instead can be as accurate as the hardware allows (microsecond accuracy is typical of modern hardware).
See http://man7.org/linux/man-pages/man7/time.7.html for more information.
On Windows timeout values in most (Win32) APIs are limited to 1 ms resolution. The accuracy is even worse. The default timer resolution is 64 Hz which allows an accuracy of 15.6 ms. There are functions like QueryPerformanceCounter
which allow a more accurate measurement of time, unfortunately there is no Timer API which works with higher resolutions. One trick to work around that problem is to use the function timeBeginPeriod
to increase the internal timer frequency. Using timeBeginPeriod(1)
you can configure a period of 1 ms so that the timer runs at a frequency of 1000 Hz, but this comes with costs as well. The Windows timer interrupt is a global resource and ticks at one rate for the entire system. That means that a single program that raises the timer frequency affects the behavior of the entire system. Increasing this frequency also affects battery life when running on laptops.
Windows has no support for POSIX timers. To make this example working on Windows, this SDK version ships with a new third-party library pthread-win32
(not to be confused with pthreads-win32), which implements the POSIX timer API for Windows. Internally it uses the Windows API functions CreateTimerQueue
, CreateTimerQueueTimer
, etc., which are available since Windows XP and newer. (This API is not available on Windows CE). This library implements also a sort of fake-signals to make signal based notifications working on Windows.
You find pthread-win32 sources in the folder src/third-party/pthread-win32
.