What You’ll Learn 🧩
Use Basic, SoftSync, and ClockProvider modes to integrate your DAQ device into DewesoftX
Build and install a custom plugin simulator, then add it via Settings → Devices
Structure plugin code using OOP: implement base and mode-specific classes for modular design
Implement Basic Mode to push data streams into DewesoftX in real-time
Manage SoftSync Mode: synchronize sample collection to external timing sources and minimize drift
Become a Clock Provider plugin: serve as the timebase source for DewesoftX, controlling timestamps and scheduling
Apply signal smoothing, handle jitter, and mitigate clock drift—critical for reliable data acquisition
Replace the built-in simulator with your own hardware-based device implementation
Course overview
This advanced course builds on initial plugin development skills, taking you into the realm of fully integrated DAQ device support in DewesoftX. It begins with setting up a simulator-based plugin and adding it to Dewesoft via Settings → Devices, selecting modes like Basic, SoftSync, or ClockProvider to define plugin behavior.
Next, you’ll explore three core operational modes:
Basic Mode: delivers raw sample data into DewesoftX’s processing chain directly.
SoftSync Mode: links incoming data to software-generated timing events, smoothing timestamp jitter and drift for better synchronization.
ClockProvider Mode: gives you full control—your plugin becomes the master clock, dictating when samples are captured and timestamped.
The course uses an object-oriented structure, where common logic lives in a BaseDeviceMode class and each mode inherits and overrides only the necessary behavior. You’ll dive into crucial techniques like data smoothing, latency handling, and clock drift compensation to ensure high-quality data streams.
By writing your own simulator and later switching it out for real hardware, you’ll gain hands-on knowledge for connecting almost any DAQ system to DewesoftX. By course end, you’ll be on the path to plugin mastery—capable of designing, implementing, and deploying advanced acquisition-level plugins that act as first-class DAQ devices within DewesoftX.
Page 1 of 10
Introduction to advanced custom plugin development in C++
Dewesoft, as known today can be used in many interesting ways like measuring the room temperature if digital thermometer is attached, or get the sound intensity from your instrument. Or, perhaps you would like to watch a video of stars from your couch while the camera is doing all the work? Or do you have any other DAQ device and want to insert its data into the Dewesoft? The second Pro Tutorial about C++ plugin development will teach you how to do just that!
Well, in this Pro Tutorial we will not be looking at the stars but we will teach you how to write C++ plugin so you will be able to do that on your own. For that reason we will use a simple simulator, which will be used as an actual DAQ device. After reading this Pro Tutorial, feel free to replace the simulator with any DAQ device and do whatever you want with its data.
Also, keep in mind that this is the second Pro Tutorial about C++ plugin Development. Before continuing it is recommended to read and understand the first Pro Tutorial about C++ plugin development which is mostly focused on how to create your own user interface, how to save and load XML settings, setting the input and output channels as well as reading from them, etc., by creating your very own latch math module.
Page 1 of 10
How to install the example plugin?
In this Pro Tutorial, we will cover different ways of reading and inserting the data from (simulated) devices to Dewesoft. We will do this by simulating a few of the most widely used modes:
Basic mode
SoftSync mode
Clock provider mode
When using the above-mentioned modes, Dewesoft will help you by providing already existing classes and functions. By using those classes you will be able to insert device data into Dewesoft in a short manner of time.
The example of the plugin used in this Pro Tutorial can be found in the Daq_Plugin folder.
When we run the plugin we have to manually add it to Dewesoft. We do this by clicking the Settings > Devices, then pressing the button and in the list that appears we choose the Test DAQ plugin.

Page 1 of 10
How the modes are structured?
It is hard to follow the code flow if all the code is written in one unit. This is the reason why we created different classes whose header and definition files can be found in the utils folder.
BaseDeviceMode class
This is our base class, classes that follow will inherit from it. It contains all the functions which we need to simulate different modes. Some functions are virtual (that means that function in the base class will be re-defined in child classes) because different modes work differently and require something that is not needed in other modes (eg. Start function). Other functions, which are not virtual, are common to all modes, therefore they do not need to be re-defined in child classes (eg. GetTime function).
A virtual function can be recognized by keyword virtual before function prototype. We will now show an example of re-defining Start function inside base class and child class SoftSync
.
1virtual Device::HANDLE Start(Device device);
The definition for Start function inside SimulatorModeBase.cpp
looks like this:
1Device::HANDLE BaseDeviceMode::Start(Device device)
2{
3 Device::HANDLE handle = device.Connect();
4 Device::AcquisitionStart(handle);
5 return handle;
6}
The return value of device.Connect()
is a handle, which is used as a parameter to every function in Device
class.
Basic mode
Represents a child class of BaseDeviceMode, virtual methods are re-defined here. The definition for Start function in BaseDeviceMode class is enough so there is no need to re-define it in BasicMode class.
Since Basic mode is "just basic", we will simplify it as much as possible. This is why we re-define the GetData function to only insert one sample per block of data. This sample will be the first sample in the data vector. We do this by saving the first value to a new variable, clearing the data vector and then inserting the variable back into the vector. We need to return a vector with our sample and not just the sample because of the definition of the GetData function in BaseDeviceMode class.
1std::vector<double> BasicMode::GetData(int numberOfsamples)
2{
3 std::vector<double> data;
4 DaqSimulator::Device::GetData(deviceHandler, &data);
5 if (!data.empty())
6 data.resize(1);
7 return data;
8}
SoftSync Mode
Represents a child class of BaseDeviceMode, virtual methods are re-defined here.
Since SoftSync
mode needs some extra initialization for private members, we will do this in the Start function. Therefore, we will have to re-define it. The redefinition starts in the header file with the function prototype by keyword override after the parentheses, that indicate the end of arguments. Override ensures that the function is virtual and is overriding a virtual function from a base class. An example of a re-defined Start function inside SoftSyncMode.h can be seen below.
1Device::HANDLE Start(Device device) override
Start function definition inside SoftSyncMode.cpp:
1Device::HANDLE SoftSyncMode::Start(Device device)
2{
3 HANDLE deviceHandler = BaseDeviceMode::Start(device);
4 srand(time(NULL));
5 softSync.initiate();
6 softSync.setDeviceRate(1);
7
8 startTime = Device::GetSystemTime(deviceHandler);
9 double slaveTime = Device::GetSystemTime(deviceHandler) - startTime;
10 lastSyncingTime = std::chrono::high_resolution_clock::now();
11 return deviceHandler;
12}
ClockProvider mode
Represents child class of BaseDeviceMode, virtual methods are re-defined here.
Although the ClockProvider
mode is the most complex, the Start function defined inside the base class will be enough. The ClockProvider
mode is further split into two sub-modes, one is synchronous and the other one is asynchronous clock provider mode. The ClockProvider
class will be used for both sub-modes. The only differences between the two are how the data is inserted into the output channel and providing the clock for other plugins, therefore, we can use the same class code for both modes.
Page 1 of 10
How to set the simulator parameters?
Page 1 of 10
Where the simulator code can be found?
In this tutorial, we will not cover things like storing and loading XML settings, the creation of the user interface and usage of user interface components. All those things can be found in the Basic custom plugin development in C++ Pro Tutorial.
The code for the simulator can be found in DaqDeviceSimulator project. Function prototypes can be found inside DaqDeviceSimulator.h file and are described below.
Devices in real life usually require that we connect them before we start using them and disconnect when we stop. Connect function will return the handle to the device and the Disconnect function will delete it.
1HANDLE Connect();
2void Disconnect(HANDLE device);
All devices have a name and port number in the following code we can see how we read them.
1std::string GetDeviceName(HANDLE device);
2int GetDevicePort(HANDLE device);
Reading the acquisition sample rate of the device can be done as seen in the code below. We usually use it when setting the expected sample rate of the output channel.
1int GetDeviceSampleRate(HANDLE device);
Setting the acquisition sample rate of the device is done as follows.
1void SetDeviceSampleRate(HANDLE device, int sampleRate);
Functions for reading the data from the device can be seen below.
GetData
returns the block of data values,GetTime
returns the block of timestamps when samples were acquired.
1int GetData(HANDLE device, void* data);
2void GetTime(HANDLE device, void* time, int numberOfSamples);
To start and stop the acquisition we call the next two functions.
1void AcquisitionStart(HANDLE device);
2void AcquisitionStop(HANDLE device);
Reading the precise device time can be done as seen below. If Fuzz factor is more than 0, it returns time with Fuzz factor taken into account.
1double GetSystemTime(HANDLE device, double fuzzFactor=0);
All function definitions can be found in DaqSimulator.cpp file.
Page 1 of 10
How to use basic mode?
Basic mode is the easiest one (to understand and use) among all of them. It is only used when conditions are perfect. In real life, there are many things that prevent perfect conditions, like device failures, lost samples, jitter when asking the device for device time, also clock drift should be taken into account and many other things. So if those mistakes are somehow eliminated, it is safe to use this mode.
To use the Basic mode, we have to go to Settings -> Devices tab -> Test DAQ Plugin and under Synchronization Type choose an item with Basic mode caption. The sample rate in Basic mode is determined by the Device Sample Rate combo box.

In Basic mode, we only need to override the base function GetData
. Remember that in this mode we decided to only keep the first sample in a block of data. The decision was made to emphasize the simplicity of Basic mode, and because (in general) asynchronous channels should have fewer samples than synchronous channels. Therefore overridden function in BasicMode.cpp should look like this.
1std::vector<double> BasicMode::GetData(int numberOfsamples)
2{
3 std::vector<double> data;
4 DaqSimulator::Device::GetData(deviceHandler, &data);
5 if (!data.empty())
6 data.resize(1);
7 return data;
8}
Now we insert a sample in the output channel. Since in basic mode we use Dewesoft's clock, we do not need to ask the device for the time.
1double DSTime = app->MasterClock->GetCurrentTime();
2std::vector<double> data = deviceMode->GetData();
3if (!data.empty())
4 outputChannel->AddAsyncDoubleSample(data[0], DSTime);
Page 1 of 10
How to use SoftSync mode?
When asking the device for its time there is some delay. Therefore, you will never really know if the returned device time is precise. This is the reason why we will use a SoftSync mode. Because our device is actually a simulator, meaning data that it returns is near flawless, we have implemented a fuzz factor, which changes the time that is returned from the device (to get the device time we use GetSystemTime()
function).
In order to use SoftSync mode PC Clock needs to be selected as the Clock source.

When Clock source is set to PC Clock, we have to go to Settings -> Devices tab ->Test DAQ Plugin and under "Synchronization Type" choose an item with "SoftSync mode" caption. The sample rate in SoftSync mode is determined by the value you choose in the "Device Sample Rate" combo box.
An example of corrupted data can be seen in the picture below.

Here we will see the real value of the SoftSync algorithm. It can be used for many different things, like smoothing the signal and dealing with clock drift, which appears because the device clock does not run at exactly the same rate as the reference clock. We should also mention, that in SoftSync mode, the reference clock is still the Dewesoft clock (the same as in Basic mode). In the further reading the term Master clock will refer to the reference clock, and the other clock, which uses the reference clock to be synchronized, will be called the Slave clock.
Although the sine wave is pretty obvious in the picture above, the signal itself is pretty distorted. We will try to make it more continuous by using class SoftSyncAlgorithm which is a part of CommonLib, and its functions are declared there as well. To use CommonLib you have to build it first. More information about the building is available in README.md inside CommonLib folder. We recommend that you set a system variable called COMMON_LIB_ROOT to the directory, where your commonLib was built. To use SoftSyncAlgorithm class, we have to include its header file in include Directories and add Library Directories. This is done under TestDaqPlugin properties -> Configuration properties -> VC++ Directories.

Now all we are left to do is specify the name of .lib file in TestDaqPlugin properties -> Linker -> Input -> Additional dependencies.

When everything is done, and SoftSyncAlgorithm is finally accessible we can start using it. Code part of SoftSync can be found on the following page.
Page 1 of 10
How do the SoftSync coding?
When everything is set and the SoftSyncAlgorithm is finally accessible, we can start with the coding. You can follow along by opening utils/SoftSyncMode.h and .cpp files. To begin with, we need to include the header files into our code by adding the following include to our file
1#include <commonlib/synchronization/softsyncalgorithm.h>
2using namespace Dewesoft::Utils::Synchronization;
Next, we will create a new private member in SoftSyncMode
class called softSync.
1private:
2 SoftSyncAlgorithm softSync;
This member needs some extra initialization, which is done in SoftSyncMode::Start
function. We have to override BaseDeviceMode::Start
function, since base initialization will not be enough.
1public:
2 Device::HANDLE Start(Device device) override;
Because basic initialization is still necessary, we will call BaseDeviceMode::Start
function first. Then we have to initiate the SoftSync member using softSync.initiate();
It is also a good idea to not sync master and slave time whenever we get new samples, because syncing on every getData would cause our signal to not be continuous. In our case, we will sync master and slave clock every 200 milliseconds. For that reason, we also set the start values to members startTime
and lastSyncingTime
so we will know when 200 milliseconds have passed.
SoftSync Start function should now look like this.
1Device::HANDLE SoftSyncMode::Start(Device device)
2{
3 HANDLE deviceHandler = BaseDeviceMode::Start(device);
4
5 softSync.initiate();
6 softSync.setDeviceRate(1);
7
8 ResetValues();
9
10 return deviceHandler;
11}
Besides Start function, it is important to pay attention to the following functions:
TrySynchronizingClocks(masterTime)
GetMasterTime(slaveTime)
TrySynchronizingClocks
is used for syncing master and slave clock. This is done by executing the following statement softSync.synchronizeClocks(masterClock, slaveClock);
, where masterClock is time provided by Dewesoft and slaveClock is time provided by our device. How synchronizeClocks
works is by calculating the difference between the slave clock and the master clock. Using this difference it will later adjust the slave clock. This is the function we will call every 200 milliseconds.
GetMasterTime
is a wrapper function which calls softSync.masterTimeAt(double time);
. This function should be called for every timestamp which was returned from the device, before inserting it into the output channel. What this function does is it takes the difference which was calculated in TrySynchronizingClocks
function and adds it to the timestamp value. The return value is the actual time when a specific sample value should be inserted into the output channel.
In the picture below you can see the same starting signal, which was processed by SoftSync and is now more continuous compared to the one without SoftSync. If you look closely, you can see that every once in awhile a signal has a small spike. This happens on approximately 200 milliseconds. This is the time when the TrySynchronizingClocks
function is called.

Page 1 of 10
How to use the ClockProvider mode?
In the SoftSync section, we used Dewesoft's clock as the master clock and the device clock as the slave clock, so we changed the timestamps of our samples according to the master clock. But in this mode, our device will be the clock provider, meaning that the device will be the master clock. When in Clock provider mode, our output channel can be synchronous or asynchronous type. First, we will describe the simpler one, which is the asynchronous type.
To use a Clock provider mode, we have to specify that we are the clock provider. We do this by selecting the Test DAQ Plugin item from the drop-down Time source menu.

We can later go to Settings > Devices tab > Test DAQ Plugin and under Synchronization Type choose Clock provider sync or Clock provider async. The sample rate in Clock provider mode is NOT determined by the value you choose in the Device Sample Rate combo box, therefore, it does not matter which value is selected. The sample rate is the same as the Dynamic acquisition rate, which is set in Ch. Setup under Analog in tab.
Asynchronous clock provider mode
If your device is intended to be the clock provider, Dewesoft needs to somehow be notified. This is done in plugin_impl.h file using STDMETHOD(raw_ProvidesClock(VARIANT_BOOL * Value));
function. As you can see in the function prototype, the argument Value
is passed by reference. You should set its value to true if you want your plugin to be the master clock, or to false otherwise.
Function definition should, therefore, look like this.
1STDMETHODIMP_(HRESULT __stdcall) Plugin::raw_ProvidesClock(VARIANT_BOOL * Value)
2{
3 if(bridge.providesClock())
4 *Value = TRUE;
5 else *Value = FALSE;
6 return S_OK;
7 }
Now that we have specified that our plugin will provide the clock, we have to specify the time for other plugins whenever they ask for it. We do this by keeping track of number of samples which were outputted into the output channel. We then calculate the time as a function of number of samples outputted and the device sample rate inside plugin_impl.h file using STDMETHOD(raw_OnGetClock(long * clockLow, long * clockHigh));
function:
1void DewesoftBridge::onGetClock(long * clockLow, long * clockHigh)
2{
3 int64_t samples;
4 if (OtherPluginsAskingForClock(*clockLow))
5 samples = EvaluatedSamplesCount();
6 else
7 samples = acquiredSamplesFromDevice;
8
9 *clockLow = getLowest32Bits(samples);
10 *clockHigh = getHighest32Bits(samples);
11}
The getLowest32Bits
and getHighest32Bits
functions use the number of samples acquired and split them into two 32-bit numbers, representing the lowest and highest 32 bits of the actual count. Mentioned functions are using
1long getLowest32Bits(int64_t samples)
2{
3 return samples & 0xffffffff;
4}
5
6long getHighest32Bits(int64_t samples)
7{
8 return (samples >> 32);
9}
Now all that we have to do is insert the samples to the output channel. This is done the same way as it is done in the SoftSync mode, but we do not have to modify the time which was returned from the device. Therefore we just insert sample values and sample timestamps directly into the output channel using the following lines of code.
1for (size_t i = 0; i < data.size(); i++)
2{
3 outputChannel->AddAsyncDoubleSample(data[i], time[i]);
4 acquiredSamples++;
5}
When using the asynchronous channel, the number of samples that are inserted into the output channel is determined by the programmer. In the example code above we inserted a whole block of data into the output channel whenever we received new ones. That is why we added the following line of code, acquiredSamples++;
so our DewesoftBridge::onGetClock
function will provide the correct time whenever asked for time (as already mentioned, when your plugin is clock provider, you provide a clock by specifying the number of inserted samples).
Synchronous Clock provider
Whenever we are using Synchronous Clock provider mode, we have to set our output channel to be synchronous. It is done in Dewesoft_bridge.h inside STDMETHODIMP onPreinitiate();
event. If you can not find this function, you should create your own implementation. It is done in plugin_impl.cpp file inside STDMETHODIMP Plugin::raw_OnEvent(enum EventIDs eventID, VARIANT inParam, VARIANT* outParam)
function. You have to catch a new event called evPreInitiate and add it to the switch-case block. Example code looks like this:
1STDMETHODIMP Plugin::raw_OnEvent(enum EventIDs eventID, VARIANT inParam, VARIANT* outParam)
2{
3 switch (eventID)
4 {
5 //other events
6 case evPreInitiate:
7 returnValue = eventPreinitiate();
8 break;
9 }
10}
plugin_impl.h
1STDMETHODIMP eventPreinitiate();
plugin_impl.cpp
1STDMETHODIMP Plugin::eventPreinitiate()
2{
3 bridge.onPreinitiate();
4 return S_OK;
5}
dewesoft_bridge.h
1STDMETHODIMP onPreinitiate();
dewesoft_bridge.cpp
1STDMETHODIMP_(HRESULT __stdcall) DewesoftBridge::onPreinitiate()
2{
3 if (mode == ClockProviderSync)
4 outputChannel->SetAsync(false); // check with breakpoints if it is ever set back to async
5 return S_OK;
6}
When your device is meant to provide clock and performs in synchronous mode we have to specify it by using the same code as we did in asynchronous clock provider (pay attention to STDMETHOD(raw_ProvidesClock(VARIANT_BOOL * Value))
function).
As we have already mentioned, when in Asynchronous mode, we can decide how many samples we want to insert into the output channel. Well, this is not possible when in Synchronous mode because we must insert an exact number of samples. To get this exact number, we have to call getCurrentSampleCount
function, which returns the number of all samples that could have been added since the start of the measurement, and subtract the number of samples we already outputted. That is why we need to increment the value of insertedSamples
variable whenever we output data into the output channel.
1int64_t samplesToInsert = getCurrentSampleCount() - insertedSamples;
But there are a few things which we need to take into the account when we are using the device to provide us the data. In this example, we modified the Device::GetData
function to return the exact number of samples which we need to insert. In this case, we just insert all the samples into the output channel. We do this using the following code.
1int64_t samplesToInsert = getCurrentSampleCount() - insertedSamples;
2 for (int i = 0; i < samplesToInsert; i++) {
3 outputChannel->AddDoubleSample(data[i]);
4 insertedSamples++;
5 }
But what if the device returns fewer samples than the output channel needs? Or what if the device returns more samples than needed?
In the first scenario (numberOfSamples < samplesToInsert), we have to set the calcDelay property of the output channel to the number of samples that are missing. Example of setting the calcDelay: outputChannel->CalcDelay = samplesToInsert - data.size();
In the second scenario (numberOfSamples > samplesToInsert), we have to store the extra samples, so they will be ready to be inserted in the next onGetData function call. To store the extra samples we will generate a new class member which will hold all un-inserted data.
Function void onGetClock(long* clockLow, long* clockHigh);
should remain the same as it was when in Asynchronous clock provider mode. In asynchronous we were counting a number of samples we inserted, while in synchronous, we are calculating samples by multiplying time (since the start of the measurement) with device sample rate.
1double secondsPassed = (std::chrono::high_resolution_clock::now() - startTime).count() * 1e-9;
2acquiredSamples = secondsPassed * Device::GetDeviceSampleRate(SineWaveGenerator);
Page 1 of 10