From c525efad399c8644a1cb20583d3cd69adf386a1d Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Thu, 29 Jan 2026 14:21:52 +0100 Subject: [PATCH 01/12] Add a multi reader example for same rate signals. --- examples/CMakeLists.txt | 3 +- examples/multi_reader_read_same_rates.c | 193 ++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 examples/multi_reader_read_same_rates.c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 25c41dc..ae59559 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -10,7 +10,8 @@ set(EXAMPLE_SOURCES stream_reader_read_in_loop.c read_with_formatted_timestamps.c print_data_descriptor_and_calculater_sample_rate.c - create_and_read_sample_rate_buffers.c) + create_and_read_sample_rate_buffers.c + multi_reader_read_same_rates.c) foreach(src ${EXAMPLE_SOURCES}) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c new file mode 100644 index 0000000..0a72698 --- /dev/null +++ b/examples/multi_reader_read_same_rates.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +// Configure the simulator device to have 4 channels +daqErrCode setNumberOfChannels(daqDevice* device, daqInt num); + +daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { + daqErrCode err = DAQ_SUCCESS; + + daqMultiReaderBuilder* builder = NULL; + daqMultiReaderBuilder_createMultiReaderBuilder(&builder); + + daqMultiReaderBuilder_setValueReadType(builder, daqSampleTypeFloat64); + daqMultiReaderBuilder_setDomainReadType(builder, daqSampleTypeInt64); + + daqSizeT signalCount = 0; + daqList_getCount(signals, &signalCount); + for (daqSizeT i = 0; i < signalCount; ++i) { + daqSignal* signal = NULL; + daqList_getItemAt(signals, i, &signal); + if (signal == NULL) { + err = DAQ_ERR_GENERALERROR; + break; + } + daqMultiReaderBuilder_addSignal(builder, signal); + daqReleaseRef(signal); + } + daqMultiReaderBuilder_build(builder, reader); + daqReleaseRef(builder); + + return err; +} + +daqErrCode handleEvent(daqMultiReaderStatus* status, daqSizeT* sampleRate) { + daqErrCode err = DAQ_SUCCESS; + + // NOTE: MultiReaderStatus cannot use the ReaderStatus interface for getting the event packet + daqEventPacket* eventPacket = NULL; + daqMultiReaderStatus_getMainDescriptor(status, &eventPacket); + + daqString* eventId = NULL; + daqEventPacket_getEventId(eventPacket, &eventId); + + daqBool check = False; + daqString* checkStr = NULL; + daqString_createString(&checkStr, "DATA_DESCRIPTOR_CHANGED"); + + daqBaseObject_equals(eventId, checkStr, &check); + + daqReleaseRef(checkStr); + daqReleaseRef(eventId); + + if (check == False) + { + daqReleaseRef(eventPacket); + return DAQ_ERR_INVALID_DATA; + } + + daqDict* parameters = NULL; + daqEventPacket_getParameters(eventPacket, ¶meters); + + daqString* domainDescriptorStr = NULL; + daqString_createString(&domainDescriptorStr, "DomainDataDescriptor"); + + daqDataDescriptor* domainDescriptor = NULL; + daqDict_get(parameters, domainDescriptorStr, (daqBaseObject**) &domainDescriptor); + + getSampleRate(sampleRate, domainDescriptor); + + daqReleaseRef(domainDescriptorStr); + daqReleaseRef(domainDescriptor); + daqReleaseRef(parameters); + daqReleaseRef(eventPacket); + return err; +} + +void readDataSameRateSignals(daqList* signals) +{ + daqMultiReader* multireader = NULL; + createMultiReader(signals, &multireader); + daqReader* multireaderAsReader = daqQueryInterfacePtr(multireader, DAQ_READER_INTF_ID); + + daqSizeT signalCount = 0; + daqList_getCount(signals, &signalCount); + + daqSizeT bufferSize = 0; + void** dataBuffers = malloc(signalCount * sizeof(void*)); + daqBool buffersAllocated = False; + + for (daqSizeT readCount = 0; readCount < 20; ++readCount){ + daqSizeT availableCount = 0; + daqReader_getAvailableCount(multireaderAsReader, &availableCount); + + daqSizeT count = min(bufferSize, availableCount); + + daqMultiReaderStatus* status = NULL; + daqMultiReader_read(multireader, dataBuffers, &count, 0, &status); + daqReaderStatus* statusAsReaderStatus = daqQueryInterfacePtr(status, DAQ_READER_STATUS_INTF_ID); + + daqReadStatus reportedStatus; + daqReaderStatus_getReadStatus(statusAsReaderStatus, &reportedStatus); + if (reportedStatus == daqReadStatusEvent) { + daqSizeT sampleRate; + handleEvent(status, &sampleRate); + + // Buffer size for 100ms worth of samples + bufferSize = sampleRate / 10; + + for (daqSizeT i = 0; i < signalCount; ++i) { + if (bufferSize == 0) { + continue; + } + if (buffersAllocated) { + free(dataBuffers[i]); + } + dataBuffers[i] = malloc(bufferSize * sizeof(double)); + } + buffersAllocated = bufferSize != 0; + } + else if (reportedStatus == daqReadStatusOk && count > 0) { + printf("----- DATA -----\n"); + for (daqSizeT sample = 0; sample < count; ++sample) { + for (daqSizeT i = 0; i < signalCount; ++i) { + double* buffer = (double*)dataBuffers[i]; + if (buffer == NULL) { + continue; + } + printf("%lf; ", buffer[sample]); + } + printf("\n"); + } + } + + + daqReleaseRef(statusAsReaderStatus); + daqReleaseRef(status); + Sleep(50); + } + + if (buffersAllocated) { + for (daqSizeT i = 0; i < signalCount; ++i) { + free(dataBuffers[i]); + } + } + free(dataBuffers); + + daqReleaseRef(multireaderAsReader); + daqReleaseRef(multireader); +} + +int main(void) { + daqInstance* simulatorInstance = NULL; + setupSimulator(&simulatorInstance); + daqInstance* instance = NULL; + daqDevice* device = NULL; + addSimulator(&device, &instance); + + //setNumberOfChannels(device, 4); + + daqList* signals; + daqDevice_getSignalsRecursive(device, &signals, NULL); + + readDataSameRateSignals(signals); + + daqReleaseRef(signals); + daqReleaseRef(device); + daqReleaseRef(instance); + daqReleaseRef(simulatorInstance); + return 0; +} + +// Configure the simulator device to have 4 channels +daqErrCode setNumberOfChannels(daqDevice* device, daqInt num) { + daqString* numOfChannelsStr = NULL; + daqString_createString(&numOfChannelsStr, "NumberOfChannels"); + + daqInteger* number = NULL; + daqInteger_createInteger(&number, 4); + + daqPropertyObject* propObj = NULL; + daqQueryInterface(device, DAQ_PROPERTY_OBJECT_INTF_ID, &propObj); + + daqErrCode err = daqPropertyObject_setPropertyValue(propObj, numOfChannelsStr, number); + printf("Set number of channels exited with: %d", (int)err); + + daqReleaseRef(propObj); + daqReleaseRef(number); + daqReleaseRef(numOfChannelsStr); + + return err; +} \ No newline at end of file From 7a3fc52fd667a120d1cc55021b8367415f4a5db8 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 09:49:02 +0100 Subject: [PATCH 02/12] Time stamp calculation. --- examples/multi_reader_read_same_rates.c | 193 +++++++++++++++++++----- examples/util_headers/daq_utils.h | 19 +++ 2 files changed, 173 insertions(+), 39 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 0a72698..4f14a2b 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -3,10 +3,33 @@ #include #include -// Configure the simulator device to have 4 channels -daqErrCode setNumberOfChannels(daqDevice* device, daqInt num); - -daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { +struct DomainMetadata +{ + daqSizeT sampleRate; + daqInt ruleStart; + daqInt ruleDelta; + daqInt referenceDomainOffset; +}; + +daqInt getOffsetFromStatus(daqReaderStatus* status); + +/** +* Extracts sample rate, start and delta from linear data rule and updates metadata fields. +*/ +daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata); + +/** +* Extract reference domain info offset from reference domain info object and updates the metadata field. +*/ +daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata); + +/** +* Returns True if the packet represents data descriptor change. +*/ +daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket); + +daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) +{ daqErrCode err = DAQ_SUCCESS; daqMultiReaderBuilder* builder = NULL; @@ -33,26 +56,15 @@ daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { return err; } -daqErrCode handleEvent(daqMultiReaderStatus* status, daqSizeT* sampleRate) { +daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* metadata) +{ daqErrCode err = DAQ_SUCCESS; // NOTE: MultiReaderStatus cannot use the ReaderStatus interface for getting the event packet daqEventPacket* eventPacket = NULL; daqMultiReaderStatus_getMainDescriptor(status, &eventPacket); - - daqString* eventId = NULL; - daqEventPacket_getEventId(eventPacket, &eventId); - daqBool check = False; - daqString* checkStr = NULL; - daqString_createString(&checkStr, "DATA_DESCRIPTOR_CHANGED"); - - daqBaseObject_equals(eventId, checkStr, &check); - - daqReleaseRef(checkStr); - daqReleaseRef(eventId); - - if (check == False) + if (isDataDescriptorChangeEvent(eventPacket) == False) { daqReleaseRef(eventPacket); return DAQ_ERR_INVALID_DATA; @@ -67,7 +79,8 @@ daqErrCode handleEvent(daqMultiReaderStatus* status, daqSizeT* sampleRate) { daqDataDescriptor* domainDescriptor = NULL; daqDict_get(parameters, domainDescriptorStr, (daqBaseObject**) &domainDescriptor); - getSampleRate(sampleRate, domainDescriptor); + processDataRule(domainDescriptor, metadata); + processReferenceDomainInfo(domainDescriptor, metadata); daqReleaseRef(domainDescriptorStr); daqReleaseRef(domainDescriptor); @@ -89,6 +102,7 @@ void readDataSameRateSignals(daqList* signals) void** dataBuffers = malloc(signalCount * sizeof(void*)); daqBool buffersAllocated = False; + struct DomainMetadata domain = { 1, 0, 1, 0 }; for (daqSizeT readCount = 0; readCount < 20; ++readCount){ daqSizeT availableCount = 0; daqReader_getAvailableCount(multireaderAsReader, &availableCount); @@ -102,11 +116,10 @@ void readDataSameRateSignals(daqList* signals) daqReadStatus reportedStatus; daqReaderStatus_getReadStatus(statusAsReaderStatus, &reportedStatus); if (reportedStatus == daqReadStatusEvent) { - daqSizeT sampleRate; - handleEvent(status, &sampleRate); + handleEvent(status, &domain); // Buffer size for 100ms worth of samples - bufferSize = sampleRate / 10; + bufferSize = domain.sampleRate / 10; for (daqSizeT i = 0; i < signalCount; ++i) { if (bufferSize == 0) { @@ -120,8 +133,13 @@ void readDataSameRateSignals(daqList* signals) buffersAllocated = bufferSize != 0; } else if (reportedStatus == daqReadStatusOk && count > 0) { - printf("----- DATA -----\n"); + daqInt readOffset = getOffsetFromStatus(statusAsReaderStatus); + daqInt readStartTick = domain.ruleStart + domain.referenceDomainOffset + readOffset; + + printf("\n-- TIMESTAMP --- | -------- DATA (%lld) --------\n", readCount); for (daqSizeT sample = 0; sample < count; ++sample) { + daqInt sampleTick = readStartTick + sample * domain.ruleDelta; + printf("%lld | ", sampleTick); for (daqSizeT i = 0; i < signalCount; ++i) { double* buffer = (double*)dataBuffers[i]; if (buffer == NULL) { @@ -157,8 +175,6 @@ int main(void) { daqDevice* device = NULL; addSimulator(&device, &instance); - //setNumberOfChannels(device, 4); - daqList* signals; daqDevice_getSignalsRecursive(device, &signals, NULL); @@ -171,23 +187,122 @@ int main(void) { return 0; } -// Configure the simulator device to have 4 channels -daqErrCode setNumberOfChannels(daqDevice* device, daqInt num) { - daqString* numOfChannelsStr = NULL; - daqString_createString(&numOfChannelsStr, "NumberOfChannels"); +daqInt getOffsetFromStatus(daqReaderStatus* status) +{ + daqNumber* offsetNum = NULL; + daqReaderStatus_getOffset(status, &offsetNum); + daqInt offset = 0; + daqNumber_getIntValue(offsetNum, &offset); + + daqReleaseRef(offsetNum); + return offset; +} - daqInteger* number = NULL; - daqInteger_createInteger(&number, 4); +daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out) +{ + daqErrCode err = DAQ_SUCCESS; - daqPropertyObject* propObj = NULL; - daqQueryInterface(device, DAQ_PROPERTY_OBJECT_INTF_ID, &propObj); + daqString* keyStr = NULL; + err = daqString_createString(&keyStr, key); - daqErrCode err = daqPropertyObject_setPropertyValue(propObj, numOfChannelsStr, number); - printf("Set number of channels exited with: %d", (int)err); + if (err) + return err; - daqReleaseRef(propObj); - daqReleaseRef(number); - daqReleaseRef(numOfChannelsStr); + daqBaseObject* obj = NULL; + err = daqDict_get(dict, keyStr, &obj); + daqReleaseRef(keyStr); + if (err) + return err; + + err = daqQueryInterface(obj, DAQ_NUMBER_INTF_ID, out); + daqReleaseRef(obj); return err; -} \ No newline at end of file +} + +daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata) +{ + daqDataRule* dataRule = NULL; + daqDataDescriptor_getRule(domainDataDescriptor, &dataRule); + + if (!checkIsLinearRule(dataRule)) + { + printf("Data rule of the signal is not linear, therefore we cannot calculate sample rate."); + daqReleaseRef(dataRule); + return DAQ_ERR_INVALID_DATA; + } + + daqRatio* ratio = NULL; + daqDataDescriptor_getTickResolution(domainDataDescriptor, &ratio); + + daqDict* parametersDataRule = NULL; + daqDataRule_getParameters(dataRule, ¶metersDataRule); + + + daqNumber* delta = NULL; + getNumberFromDict(parametersDataRule, "delta", &delta); + + daqNumber* start = NULL; + getNumberFromDict(parametersDataRule, "start", &start); + + calculateSampleRate(&(metadata->sampleRate), ratio, delta); + daqNumber_getIntValue(delta, &(metadata->ruleDelta)); + daqNumber_getIntValue(start, &(metadata->ruleStart)); + + daqReleaseRef(delta); + daqReleaseRef(start); + daqReleaseRef(parametersDataRule); + daqReleaseRef(ratio); + daqReleaseRef(dataRule); + + return DAQ_SUCCESS; +} + +daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata) +{ + daqErrCode err = DAQ_SUCCESS; + + daqReferenceDomainInfo* domainInfo = NULL; + err = daqDataDescriptor_getReferenceDomainInfo(domainDataDescriptor, &domainInfo); + + // Reference domain info is not mandatory + if (err || domainInfo == NULL) { + printf("Reference domain info unavailable."); + metadata->referenceDomainOffset = 0; + return DAQ_SUCCESS; + } + + daqInteger* refDomainOffset = NULL; + err = daqReferenceDomainInfo_getReferenceDomainOffset(domainInfo, &refDomainOffset); + + // Reference domain info is not mandatory + if (err || refDomainOffset == NULL) { + printf("Reference domain info unavailable."); + metadata->referenceDomainOffset = 0; + daqReleaseRef(domainInfo); + return DAQ_SUCCESS; + } + + err = daqInteger_getValue(refDomainOffset, &(metadata->referenceDomainOffset)); + + daqReleaseRef(refDomainOffset); + daqReleaseRef(domainInfo); + + return err; +} + +daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket) +{ + daqString* eventId = NULL; + daqEventPacket_getEventId(eventPacket, &eventId); + + daqString* checkStr = NULL; + daqString_createString(&checkStr, "DATA_DESCRIPTOR_CHANGED"); + + daqBool check = False; + daqBaseObject_equals(eventId, checkStr, &check); + + daqReleaseRef(checkStr); + daqReleaseRef(eventId); + return check; +} diff --git a/examples/util_headers/daq_utils.h b/examples/util_headers/daq_utils.h index 3d7d86c..a25d10f 100644 --- a/examples/util_headers/daq_utils.h +++ b/examples/util_headers/daq_utils.h @@ -124,13 +124,32 @@ static inline daqErrCode setupSimulator(daqInstance** instance) daqReleaseRef(serialNumberDefaultValue); daqReleaseRef(serialNumberPropertyName); + daqProperty* numberOfChannelsProperty = NULL; + + daqBoolean* numberOfChannelsVisible = NULL; + daqBoolean_createBoolean(&numberOfChannelsVisible, True); + + daqString* numberOfChannelsPropertyName = NULL; + daqString_createString(&numberOfChannelsPropertyName, "NumberOfChannels"); + + daqInteger* numberOfChannelsDefaultValue = NULL; + daqInteger_createInteger(&numberOfChannelsDefaultValue, 8); + + daqProperty_createIntProperty(&numberOfChannelsProperty, numberOfChannelsPropertyName, numberOfChannelsDefaultValue, numberOfChannelsVisible); + + daqReleaseRef(numberOfChannelsVisible); + daqReleaseRef(numberOfChannelsDefaultValue); + daqReleaseRef(numberOfChannelsPropertyName); + daqPropertyObject_addProperty(config, nameProperty); daqPropertyObject_addProperty(config, localIdProperty); daqPropertyObject_addProperty(config, serialNumberProperty); + daqPropertyObject_addProperty(config, numberOfChannelsProperty); daqReleaseRef(nameProperty); daqReleaseRef(localIdProperty); daqReleaseRef(serialNumberProperty); + daqReleaseRef(numberOfChannelsProperty); daqInstanceBuilder* instanceBuilder = NULL; From be16e19819bb04c94dd0c6aacb0806aca9fa3aee Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 10:28:35 +0100 Subject: [PATCH 03/12] Changing the order of declarations and implementations. --- examples/multi_reader_read_same_rates.c | 179 +++++++++++++----------- 1 file changed, 100 insertions(+), 79 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 4f14a2b..17c38bb 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -3,6 +3,9 @@ #include #include +/** +* Set of parameters describing the time domain. +*/ struct DomainMetadata { daqSizeT sampleRate; @@ -11,7 +14,15 @@ struct DomainMetadata daqInt referenceDomainOffset; }; -daqInt getOffsetFromStatus(daqReaderStatus* status); +/** +* Create multi reader from a list of signals. +*/ +daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader); + +/** +* Handle event in the daqMultiReaderStatus by updating the DomainMetadata object. +*/ +daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* metadata); /** * Extracts sample rate, start and delta from linear data rule and updates metadata fields. @@ -28,66 +39,15 @@ daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, s */ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket); -daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) -{ - daqErrCode err = DAQ_SUCCESS; - - daqMultiReaderBuilder* builder = NULL; - daqMultiReaderBuilder_createMultiReaderBuilder(&builder); - - daqMultiReaderBuilder_setValueReadType(builder, daqSampleTypeFloat64); - daqMultiReaderBuilder_setDomainReadType(builder, daqSampleTypeInt64); - - daqSizeT signalCount = 0; - daqList_getCount(signals, &signalCount); - for (daqSizeT i = 0; i < signalCount; ++i) { - daqSignal* signal = NULL; - daqList_getItemAt(signals, i, &signal); - if (signal == NULL) { - err = DAQ_ERR_GENERALERROR; - break; - } - daqMultiReaderBuilder_addSignal(builder, signal); - daqReleaseRef(signal); - } - daqMultiReaderBuilder_build(builder, reader); - daqReleaseRef(builder); - - return err; -} - -daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* metadata) -{ - daqErrCode err = DAQ_SUCCESS; - - // NOTE: MultiReaderStatus cannot use the ReaderStatus interface for getting the event packet - daqEventPacket* eventPacket = NULL; - daqMultiReaderStatus_getMainDescriptor(status, &eventPacket); - - if (isDataDescriptorChangeEvent(eventPacket) == False) - { - daqReleaseRef(eventPacket); - return DAQ_ERR_INVALID_DATA; - } - - daqDict* parameters = NULL; - daqEventPacket_getParameters(eventPacket, ¶meters); - - daqString* domainDescriptorStr = NULL; - daqString_createString(&domainDescriptorStr, "DomainDataDescriptor"); - - daqDataDescriptor* domainDescriptor = NULL; - daqDict_get(parameters, domainDescriptorStr, (daqBaseObject**) &domainDescriptor); - - processDataRule(domainDescriptor, metadata); - processReferenceDomainInfo(domainDescriptor, metadata); +/** +* Get offset to the first sample in the read buffer. +*/ +daqInt getOffsetFromStatus(daqReaderStatus* status); - daqReleaseRef(domainDescriptorStr); - daqReleaseRef(domainDescriptor); - daqReleaseRef(parameters); - daqReleaseRef(eventPacket); - return err; -} +/** +* Get a daqNumber from daqDict object. +*/ +daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out); void readDataSameRateSignals(daqList* signals) { @@ -187,36 +147,64 @@ int main(void) { return 0; } -daqInt getOffsetFromStatus(daqReaderStatus* status) +daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { - daqNumber* offsetNum = NULL; - daqReaderStatus_getOffset(status, &offsetNum); - daqInt offset = 0; - daqNumber_getIntValue(offsetNum, &offset); + daqErrCode err = DAQ_SUCCESS; - daqReleaseRef(offsetNum); - return offset; + daqMultiReaderBuilder* builder = NULL; + daqMultiReaderBuilder_createMultiReaderBuilder(&builder); + + daqMultiReaderBuilder_setValueReadType(builder, daqSampleTypeFloat64); + daqMultiReaderBuilder_setDomainReadType(builder, daqSampleTypeInt64); + + daqSizeT signalCount = 0; + daqList_getCount(signals, &signalCount); + for (daqSizeT i = 0; i < signalCount; ++i) { + daqSignal* signal = NULL; + daqList_getItemAt(signals, i, &signal); + if (signal == NULL) { + err = DAQ_ERR_GENERALERROR; + break; + } + daqMultiReaderBuilder_addSignal(builder, signal); + daqReleaseRef(signal); + } + daqMultiReaderBuilder_build(builder, reader); + daqReleaseRef(builder); + + return err; } -daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out) +daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* metadata) { daqErrCode err = DAQ_SUCCESS; - daqString* keyStr = NULL; - err = daqString_createString(&keyStr, key); + // NOTE: MultiReaderStatus cannot use the ReaderStatus interface for getting the event packet + daqEventPacket* eventPacket = NULL; + daqMultiReaderStatus_getMainDescriptor(status, &eventPacket); - if (err) - return err; + if (isDataDescriptorChangeEvent(eventPacket) == False) + { + daqReleaseRef(eventPacket); + return DAQ_ERR_INVALID_DATA; + } - daqBaseObject* obj = NULL; - err = daqDict_get(dict, keyStr, &obj); - daqReleaseRef(keyStr); + daqDict* parameters = NULL; + daqEventPacket_getParameters(eventPacket, ¶meters); - if (err) - return err; + daqString* domainDescriptorStr = NULL; + daqString_createString(&domainDescriptorStr, "DomainDataDescriptor"); - err = daqQueryInterface(obj, DAQ_NUMBER_INTF_ID, out); - daqReleaseRef(obj); + daqDataDescriptor* domainDescriptor = NULL; + daqDict_get(parameters, domainDescriptorStr, (daqBaseObject**)&domainDescriptor); + + processDataRule(domainDescriptor, metadata); + processReferenceDomainInfo(domainDescriptor, metadata); + + daqReleaseRef(domainDescriptorStr); + daqReleaseRef(domainDescriptor); + daqReleaseRef(parameters); + daqReleaseRef(eventPacket); return err; } @@ -306,3 +294,36 @@ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket) daqReleaseRef(eventId); return check; } + +daqInt getOffsetFromStatus(daqReaderStatus* status) +{ + daqNumber* offsetNum = NULL; + daqReaderStatus_getOffset(status, &offsetNum); + daqInt offset = 0; + daqNumber_getIntValue(offsetNum, &offset); + + daqReleaseRef(offsetNum); + return offset; +} + +daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out) +{ + daqErrCode err = DAQ_SUCCESS; + + daqString* keyStr = NULL; + err = daqString_createString(&keyStr, key); + + if (err) + return err; + + daqBaseObject* obj = NULL; + err = daqDict_get(dict, keyStr, &obj); + daqReleaseRef(keyStr); + + if (err) + return err; + + err = daqQueryInterface(obj, DAQ_NUMBER_INTF_ID, out); + daqReleaseRef(obj); + return err; +} From 77f3a4c615d25032a82201d75d4ad6c129827fdd Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 11:06:01 +0100 Subject: [PATCH 04/12] Improvements --- examples/multi_reader_read_same_rates.c | 128 ++++++++++++++++-------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 17c38bb..a5f2707 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -14,6 +14,17 @@ struct DomainMetadata daqInt referenceDomainOffset; }; +/* +* Read data from a list of signals. The signals should have the same sample rate. +* +* This function is the core of this example. It demonstrates how to use multi reader +* to read samples from multiple aligned signals. The reading process consists of a +* simple loop with a timed sleep to let samples accumulate. The multi reader user +* only needs to allocate appropriate buffers, but there is no need to handle individual +* packets. +*/ +void readDataSameRateSignals(daqList* signals); + /** * Create multi reader from a list of signals. */ @@ -49,6 +60,38 @@ daqInt getOffsetFromStatus(daqReaderStatus* status); */ daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out); +/** +* Allocate buffers to hold n doubles. It is assumed that buffers has enough allocated memory to hold count +* number of pointers. +*/ +daqBool allocateBuffers(void** buffers, daqSizeT count, daqSizeT n); + +/** +* Calls free on count number of pointer inside buffers if allocated is True. +*/ +void freeIfAllocated(void** buffers, daqSizeT count, daqBool allocated); + +int main(void) { + // Setup simulated device generating samples on 8 channels. + daqInstance* simulatorInstance = NULL; + setupSimulator(&simulatorInstance); + daqInstance* instance = NULL; + daqDevice* device = NULL; + addSimulator(&device, &instance); + + daqList* signals; + daqDevice_getSignalsRecursive(device, &signals, NULL); + + // Start reading samples from the signals. + readDataSameRateSignals(signals); + + daqReleaseRef(signals); + daqReleaseRef(device); + daqReleaseRef(instance); + daqReleaseRef(simulatorInstance); + return 0; +} + void readDataSameRateSignals(daqList* signals) { daqMultiReader* multireader = NULL; @@ -58,15 +101,16 @@ void readDataSameRateSignals(daqList* signals) daqSizeT signalCount = 0; daqList_getCount(signals, &signalCount); - daqSizeT bufferSize = 0; - void** dataBuffers = malloc(signalCount * sizeof(void*)); + void** dataBuffers = calloc(signalCount, sizeof(void*)); + daqBool buffersAllocated = False; + daqSizeT bufferSize = 0; struct DomainMetadata domain = { 1, 0, 1, 0 }; - for (daqSizeT readCount = 0; readCount < 20; ++readCount){ + for (daqSizeT readCount = 0; readCount < 20; ++readCount) { daqSizeT availableCount = 0; daqReader_getAvailableCount(multireaderAsReader, &availableCount); - + daqSizeT count = min(bufferSize, availableCount); daqMultiReaderStatus* status = NULL; @@ -81,30 +125,28 @@ void readDataSameRateSignals(daqList* signals) // Buffer size for 100ms worth of samples bufferSize = domain.sampleRate / 10; - for (daqSizeT i = 0; i < signalCount; ++i) { - if (bufferSize == 0) { - continue; - } - if (buffersAllocated) { - free(dataBuffers[i]); - } - dataBuffers[i] = malloc(bufferSize * sizeof(double)); - } - buffersAllocated = bufferSize != 0; + freeIfAllocated(dataBuffers, signalCount, buffersAllocated); + buffersAllocated = allocateBuffers(dataBuffers, signalCount, bufferSize); + bufferSize = buffersAllocated ? bufferSize : 0; } else if (reportedStatus == daqReadStatusOk && count > 0) { + // Get offset of the first sample daqInt readOffset = getOffsetFromStatus(statusAsReaderStatus); + + // Get tick of the first sample daqInt readStartTick = domain.ruleStart + domain.referenceDomainOffset + readOffset; printf("\n-- TIMESTAMP --- | -------- DATA (%lld) --------\n", readCount); for (daqSizeT sample = 0; sample < count; ++sample) { + // Calculate tick according to linear data rule. daqInt sampleTick = readStartTick + sample * domain.ruleDelta; + printf("%lld | ", sampleTick); for (daqSizeT i = 0; i < signalCount; ++i) { - double* buffer = (double*)dataBuffers[i]; - if (buffer == NULL) { + if (dataBuffers[i] == NULL) { continue; } + double* buffer = (double*)dataBuffers[i]; printf("%lf; ", buffer[sample]); } printf("\n"); @@ -117,36 +159,13 @@ void readDataSameRateSignals(daqList* signals) Sleep(50); } - if (buffersAllocated) { - for (daqSizeT i = 0; i < signalCount; ++i) { - free(dataBuffers[i]); - } - } + freeIfAllocated(dataBuffers, signalCount, buffersAllocated); free(dataBuffers); daqReleaseRef(multireaderAsReader); daqReleaseRef(multireader); } -int main(void) { - daqInstance* simulatorInstance = NULL; - setupSimulator(&simulatorInstance); - daqInstance* instance = NULL; - daqDevice* device = NULL; - addSimulator(&device, &instance); - - daqList* signals; - daqDevice_getSignalsRecursive(device, &signals, NULL); - - readDataSameRateSignals(signals); - - daqReleaseRef(signals); - daqReleaseRef(device); - daqReleaseRef(instance); - daqReleaseRef(simulatorInstance); - return 0; -} - daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { daqErrCode err = DAQ_SUCCESS; @@ -327,3 +346,32 @@ daqErrCode getNumberFromDict(daqDict* dict, const char* key, daqNumber** out) daqReleaseRef(obj); return err; } + +daqBool allocateBuffers(void** buffers, daqSizeT count, daqSizeT n) +{ + if (n == 0) + return False; + + for (daqSizeT i = 0; i < count; ++i) { + buffers[i] = malloc(n * sizeof(double)); + if (!buffers[i]) { + for (daqSizeT j = 0; j < i; ++j) { + free(buffers[j]); + buffers[j] = NULL; + } + return False; + } + } + return True; +} + +void freeIfAllocated(void** buffers, daqSizeT count, daqBool allocated) +{ + if (allocated == False) + return; + + for (daqSizeT i = 0; i < count; ++i) { + free(buffers[i]); + buffers[i] = NULL; + } +} \ No newline at end of file From 5f479c5231c93d4d6eb18c80f6fe0d659793d881 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 13:07:55 +0100 Subject: [PATCH 05/12] Use AI tag to filter channels and get their first signal. --- examples/multi_reader_read_same_rates.c | 109 ++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index a5f2707..e0c9938 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -25,6 +25,16 @@ struct DomainMetadata */ void readDataSameRateSignals(daqList* signals); +/** +* Add existing device. +*/ +daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const char* connectionStr); + +/** +* Get signals to read. +*/ +daqList* getSignals(daqDevice* device, daqBool useAITag); + /** * Create multi reader from a list of signals. */ @@ -72,15 +82,16 @@ daqBool allocateBuffers(void** buffers, daqSizeT count, daqSizeT n); void freeIfAllocated(void** buffers, daqSizeT count, daqBool allocated); int main(void) { - // Setup simulated device generating samples on 8 channels. - daqInstance* simulatorInstance = NULL; - setupSimulator(&simulatorInstance); + //daqInstance* simulatorInstance = NULL; daqInstance* instance = NULL; daqDevice* device = NULL; + + // Setup simulated device generating samples on 8 channels. + //setupSimulator(&simulatorInstance); addSimulator(&device, &instance); + addExistingDevice(&instance, &device, "daq://Dewesoft_DB24049746"); - daqList* signals; - daqDevice_getSignalsRecursive(device, &signals, NULL); + daqList* signals = getSignals(device, True); // Start reading samples from the signals. readDataSameRateSignals(signals); @@ -88,7 +99,7 @@ int main(void) { daqReleaseRef(signals); daqReleaseRef(device); daqReleaseRef(instance); - daqReleaseRef(simulatorInstance); + //daqReleaseRef(simulatorInstance); return 0; } @@ -166,6 +177,89 @@ void readDataSameRateSignals(daqList* signals) daqReleaseRef(multireader); } +// Helper +void printSignalGlobalId(daqSignal* signal) +{ + daqString* localId = NULL; + daqComponent_getGlobalId((daqComponent*)signal, &localId); + + printDaqFormattedString("%s\n", localId); +} + +daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const char* connectionStr) +{ + createInstance(instance, MODULE_PATH); + + daqString* connectionString = NULL; + daqString_createString(&connectionString, connectionStr); + daqErrCode err = daqDevice_addDevice((daqDevice*)*instance, device, connectionString, NULL); + daqReleaseRef(connectionString); + + if (err != DAQ_SUCCESS) + printf("Add device failed"); + return err; +} + +daqList* getSignals(daqDevice* device, daqBool useAITag) +{ + daqList* signals = NULL; + if (useAITag) { + // Create empty + daqList_createListWithElementType(&signals, DAQ_SIGNAL_INTF_ID); + + // TODO: Access via AI tag + daqString* ai = NULL; + daqString_createString(&ai, "AI"); + + daqList* tags = NULL; + daqList_createListWithElementType(&tags, DAQ_STRING_INTF_ID); + daqList_pushBack(tags, ai); + + daqSearchFilter* filter; + daqSearchFilter_createRequiredTagsSearchFilter(&filter, tags); + + // Get AI channels + daqList* channels = NULL; + daqDevice_getChannels(device, &channels, filter); + + daqIterator* iterator = NULL; + daqList_createStartIterator(channels, &iterator); + while (daqIterator_moveNext(iterator) == DAQ_SUCCESS) + { + daqFunctionBlock* ch = NULL; + daqIterator_getCurrent(iterator, &ch); + + daqList* chSignals = NULL; + daqFunctionBlock_getSignals(ch, &chSignals, NULL); + + daqSizeT cnt = 0; + daqList_getCount(chSignals, &cnt); + + if (cnt > 0) { + daqSignal* firstSignal = NULL; + daqList_getItemAt(chSignals, 0, &firstSignal); + daqList_pushBack(signals, firstSignal); + + daqReleaseRef(firstSignal); + } + + daqReleaseRef(chSignals); + daqReleaseRef(ch); + } + + daqReleaseRef(iterator); + daqReleaseRef(channels); + daqReleaseRef(tags); + daqReleaseRef(ai); + daqReleaseRef(filter); + } + else { + // Get all possible signals. + daqDevice_getSignalsRecursive(device, &signals, NULL); + } + return signals; +} + daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { daqErrCode err = DAQ_SUCCESS; @@ -186,6 +280,7 @@ daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) break; } daqMultiReaderBuilder_addSignal(builder, signal); + printSignalGlobalId(signal); daqReleaseRef(signal); } daqMultiReaderBuilder_build(builder, reader); @@ -239,6 +334,8 @@ daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct Domai return DAQ_ERR_INVALID_DATA; } + // TODO: Check that origin is 1970 + daqRatio* ratio = NULL; daqDataDescriptor_getTickResolution(domainDataDescriptor, &ratio); From 622ffc220edac7eb072fc5831a792a96e768237d Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 13:36:07 +0100 Subject: [PATCH 06/12] Support custom connection strings --- examples/multi_reader_read_same_rates.c | 39 ++++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index e0c9938..0de8e21 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -33,7 +33,7 @@ daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const c /** * Get signals to read. */ -daqList* getSignals(daqDevice* device, daqBool useAITag); +daqList* getSignals(daqDevice* device, daqBool allSignals); /** * Create multi reader from a list of signals. @@ -82,16 +82,21 @@ daqBool allocateBuffers(void** buffers, daqSizeT count, daqSizeT n); void freeIfAllocated(void** buffers, daqSizeT count, daqBool allocated); int main(void) { - //daqInstance* simulatorInstance = NULL; + daqInstance* simulatorInstance = NULL; daqInstance* instance = NULL; daqDevice* device = NULL; // Setup simulated device generating samples on 8 channels. - //setupSimulator(&simulatorInstance); - addSimulator(&device, &instance); - addExistingDevice(&instance, &device, "daq://Dewesoft_DB24049746"); + setupSimulator(&simulatorInstance); + const char* simulator = "daq://openDAQ_sim01"; + + const char* custom = "daq://Dewesoft_DB24049746"; - daqList* signals = getSignals(device, True); + daqBool useSimulator = False; + const char* connectionString = useSimulator ? simulator : custom; + + addExistingDevice(&instance, &device, connectionString); + daqList* signals = getSignals(device, useSimulator); // Start reading samples from the signals. readDataSameRateSignals(signals); @@ -99,7 +104,7 @@ int main(void) { daqReleaseRef(signals); daqReleaseRef(device); daqReleaseRef(instance); - //daqReleaseRef(simulatorInstance); + daqReleaseRef(simulatorInstance); return 0; } @@ -149,6 +154,15 @@ void readDataSameRateSignals(daqList* signals) printf("\n-- TIMESTAMP --- | -------- DATA (%lld) --------\n", readCount); for (daqSizeT sample = 0; sample < count; ++sample) { + // Only print the first and the last samples + if (sample == 2) { + printf("...\n"); + continue; + } + else if (sample > 2 && sample < count - 1) { + continue; + } + // Calculate tick according to linear data rule. daqInt sampleTick = readStartTick + sample * domain.ruleDelta; @@ -183,7 +197,7 @@ void printSignalGlobalId(daqSignal* signal) daqString* localId = NULL; daqComponent_getGlobalId((daqComponent*)signal, &localId); - printDaqFormattedString("%s\n", localId); + printDaqFormattedString(" - %s\n", localId); } daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const char* connectionStr) @@ -200,14 +214,13 @@ daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const c return err; } -daqList* getSignals(daqDevice* device, daqBool useAITag) +daqList* getSignals(daqDevice* device, daqBool allSignals) { daqList* signals = NULL; - if (useAITag) { + if (!allSignals) { // Create empty daqList_createListWithElementType(&signals, DAQ_SIGNAL_INTF_ID); - // TODO: Access via AI tag daqString* ai = NULL; daqString_createString(&ai, "AI"); @@ -270,6 +283,8 @@ daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) daqMultiReaderBuilder_setValueReadType(builder, daqSampleTypeFloat64); daqMultiReaderBuilder_setDomainReadType(builder, daqSampleTypeInt64); + printf("Signals to read:\n"); + daqSizeT signalCount = 0; daqList_getCount(signals, &signalCount); for (daqSizeT i = 0; i < signalCount; ++i) { @@ -283,6 +298,8 @@ daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) printSignalGlobalId(signal); daqReleaseRef(signal); } + printf("\n"); + daqMultiReaderBuilder_build(builder, reader); daqReleaseRef(builder); From b5c90f5b758b7d31981f2f6c18761627e16df44f Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 14:18:29 +0100 Subject: [PATCH 07/12] Review changes. --- examples/multi_reader_read_same_rates.c | 120 ++++++++++++------------ 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 0de8e21..3510831 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -28,12 +28,15 @@ void readDataSameRateSignals(daqList* signals); /** * Add existing device. */ -daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const char* connectionStr); +daqErrCode addDeviceWrapper(daqInstance** instance, daqDevice** device, const char* connectionStr); /** -* Get signals to read. +* Get the first signals of channels with tag "AI" from device. +* +* Actual devices may have a lot of signals that are unsuitable for synchronized reading. One way +* of collecting appropriate signals is to filter channels by tag "AI" and take their first signal. */ -daqList* getSignals(daqDevice* device, daqBool allSignals); +void getAIChannelSignals(daqDevice* device, daqList** signals); /** * Create multi reader from a list of signals. @@ -86,17 +89,21 @@ int main(void) { daqInstance* instance = NULL; daqDevice* device = NULL; - // Setup simulated device generating samples on 8 channels. + // This example creates a simulator device with 8 channels and connects to it. + // Note, however, that simulator creation may be omitted and connection string + // replaced with a connection string of an existing (real) device. setupSimulator(&simulatorInstance); - const char* simulator = "daq://openDAQ_sim01"; + const char* connectionString = "daq://openDAQ_sim01"; // "daq://Dewesoft_DB24049746"; + addDeviceWrapper(&instance, &device, connectionString); - const char* custom = "daq://Dewesoft_DB24049746"; + daqList* signals = NULL; + // All of the simulator's signals are appropriate for synchronized reading. For real + // devices one may need to filter the signals. One of the options is to use the function + // getAIChannelSignals. + daqDevice_getSignalsRecursive(device, &signals, NULL); + //getAIChannelSignals(device, &signals); - daqBool useSimulator = False; - const char* connectionString = useSimulator ? simulator : custom; - - addExistingDevice(&instance, &device, connectionString); - daqList* signals = getSignals(device, useSimulator); + // TODO: check sampling rates. // Start reading samples from the signals. readDataSameRateSignals(signals); @@ -138,8 +145,8 @@ void readDataSameRateSignals(daqList* signals) if (reportedStatus == daqReadStatusEvent) { handleEvent(status, &domain); - // Buffer size for 100ms worth of samples - bufferSize = domain.sampleRate / 10; + // Buffer size for 400ms worth of samples + bufferSize = (domain.sampleRate * 2) / 5; freeIfAllocated(dataBuffers, signalCount, buffersAllocated); buffersAllocated = allocateBuffers(dataBuffers, signalCount, bufferSize); @@ -152,7 +159,8 @@ void readDataSameRateSignals(daqList* signals) // Get tick of the first sample daqInt readStartTick = domain.ruleStart + domain.referenceDomainOffset + readOffset; - printf("\n-- TIMESTAMP --- | -------- DATA (%lld) --------\n", readCount); + printf("\nRead successful (%lld).\nRead %lld samples. Printing samples 0, 1 and %lld.\n", readCount, count, count-1); + printf("-- TIMESTAMP --- | -------- DATA -----------\n"); for (daqSizeT sample = 0; sample < count; ++sample) { // Only print the first and the last samples if (sample == 2) { @@ -181,7 +189,7 @@ void readDataSameRateSignals(daqList* signals) daqReleaseRef(statusAsReaderStatus); daqReleaseRef(status); - Sleep(50); + Sleep(200); } freeIfAllocated(dataBuffers, signalCount, buffersAllocated); @@ -200,7 +208,7 @@ void printSignalGlobalId(daqSignal* signal) printDaqFormattedString(" - %s\n", localId); } -daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const char* connectionStr) +daqErrCode addDeviceWrapper(daqInstance** instance, daqDevice** device, const char* connectionStr) { createInstance(instance, MODULE_PATH); @@ -214,63 +222,55 @@ daqErrCode addExistingDevice(daqInstance** instance, daqDevice** device, const c return err; } -daqList* getSignals(daqDevice* device, daqBool allSignals) +void getAIChannelSignals(daqDevice* device, daqList** signals) { - daqList* signals = NULL; - if (!allSignals) { - // Create empty - daqList_createListWithElementType(&signals, DAQ_SIGNAL_INTF_ID); + // Create empty + daqList_createListWithElementType(signals, DAQ_SIGNAL_INTF_ID); - daqString* ai = NULL; - daqString_createString(&ai, "AI"); + daqString* ai = NULL; + daqString_createString(&ai, "AI"); - daqList* tags = NULL; - daqList_createListWithElementType(&tags, DAQ_STRING_INTF_ID); - daqList_pushBack(tags, ai); + daqList* tags = NULL; + daqList_createListWithElementType(&tags, DAQ_STRING_INTF_ID); + daqList_pushBack(tags, ai); - daqSearchFilter* filter; - daqSearchFilter_createRequiredTagsSearchFilter(&filter, tags); + daqSearchFilter* filter; + daqSearchFilter_createRequiredTagsSearchFilter(&filter, tags); - // Get AI channels - daqList* channels = NULL; - daqDevice_getChannels(device, &channels, filter); + // Get AI channels + daqList* channels = NULL; + daqDevice_getChannels(device, &channels, filter); - daqIterator* iterator = NULL; - daqList_createStartIterator(channels, &iterator); - while (daqIterator_moveNext(iterator) == DAQ_SUCCESS) - { - daqFunctionBlock* ch = NULL; - daqIterator_getCurrent(iterator, &ch); - - daqList* chSignals = NULL; - daqFunctionBlock_getSignals(ch, &chSignals, NULL); + daqIterator* iterator = NULL; + daqList_createStartIterator(channels, &iterator); + while (daqIterator_moveNext(iterator) == DAQ_SUCCESS) + { + daqFunctionBlock* ch = NULL; + daqIterator_getCurrent(iterator, &ch); - daqSizeT cnt = 0; - daqList_getCount(chSignals, &cnt); + daqList* chSignals = NULL; + daqFunctionBlock_getSignals(ch, &chSignals, NULL); - if (cnt > 0) { - daqSignal* firstSignal = NULL; - daqList_getItemAt(chSignals, 0, &firstSignal); - daqList_pushBack(signals, firstSignal); + daqSizeT cnt = 0; + daqList_getCount(chSignals, &cnt); - daqReleaseRef(firstSignal); - } + if (cnt > 0) { + daqSignal* firstSignal = NULL; + daqList_getItemAt(chSignals, 0, &firstSignal); + daqList_pushBack(*signals, firstSignal); - daqReleaseRef(chSignals); - daqReleaseRef(ch); + daqReleaseRef(firstSignal); } - daqReleaseRef(iterator); - daqReleaseRef(channels); - daqReleaseRef(tags); - daqReleaseRef(ai); - daqReleaseRef(filter); - } - else { - // Get all possible signals. - daqDevice_getSignalsRecursive(device, &signals, NULL); + daqReleaseRef(chSignals); + daqReleaseRef(ch); } - return signals; + + daqReleaseRef(iterator); + daqReleaseRef(channels); + daqReleaseRef(tags); + daqReleaseRef(ai); + daqReleaseRef(filter); } daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) From 647deb17452dcf0cbfa98980eb59b802b5d783d5 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 15:50:52 +0100 Subject: [PATCH 08/12] Duration since epoch print out. --- examples/multi_reader_read_same_rates.c | 162 +++++++++++++++++++----- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 3510831..2c6749b 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -12,8 +12,24 @@ struct DomainMetadata daqInt ruleStart; daqInt ruleDelta; daqInt referenceDomainOffset; + + daqString* origin; + daqString* unitSymbol; + + daqInt resNum; + daqInt resDen; }; +/** +* Creation utility function. +*/ +struct DomainMetadata createDomainMetadata(); + +/** +* Destruction utility. +*/ +void freeDomainMetadata(struct DomainMetadata* p); + /* * Read data from a list of signals. The signals should have the same sample rate. * @@ -49,20 +65,27 @@ daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader); daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* metadata); /** -* Extracts sample rate, start and delta from linear data rule and updates metadata fields. +* Extracts sample rate, start and delta from linear data rule, referenceDomainOffset from reference domain info +* and updates metadata fields. */ -daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata); +daqErrCode updateDomainMetadata(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata); /** -* Extract reference domain info offset from reference domain info object and updates the metadata field. +* Get reference domain info offset from reference domain info object. */ -daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata); +daqErrCode getReferenceDomainOffset(daqDataDescriptor* domainDataDescriptor, daqInt* offset); /** * Returns True if the packet represents data descriptor change. */ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket); +/** +* Print absolute time stamp in format: 20483d 14h 49min 10.934s since 1970-01-01T00:00:00Z. The precision +* of this format is 1ms, so two samples within a millisecond may have the same time stamp printed out. +*/ +void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick); + /** * Get offset to the first sample in the read buffer. */ @@ -88,20 +111,26 @@ int main(void) { daqInstance* simulatorInstance = NULL; daqInstance* instance = NULL; daqDevice* device = NULL; - - // This example creates a simulator device with 8 channels and connects to it. - // Note, however, that simulator creation may be omitted and connection string - // replaced with a connection string of an existing (real) device. setupSimulator(&simulatorInstance); - const char* connectionString = "daq://openDAQ_sim01"; // "daq://Dewesoft_DB24049746"; - addDeviceWrapper(&instance, &device, connectionString); - + daqList* signals = NULL; + + // This example creates a simulator device with 8 channels and connects to it. + // Connection string may be replaced with a connection string of an existing + // (real) device. + // // All of the simulator's signals are appropriate for synchronized reading. For real // devices one may need to filter the signals. One of the options is to use the function // getAIChannelSignals. + + const char* connectionString = "daq://openDAQ_sim01"; + addDeviceWrapper(&instance, &device, connectionString); daqDevice_getSignalsRecursive(device, &signals, NULL); - //getAIChannelSignals(device, &signals); + + // Connect to an existing device + // const char* customString = "daq://Dewesoft_DB24049746"; + // addDeviceWrapper(&instance, &device, customString); + // getAIChannelSignals(device, &signals); // TODO: check sampling rates. @@ -129,7 +158,7 @@ void readDataSameRateSignals(daqList* signals) daqBool buffersAllocated = False; daqSizeT bufferSize = 0; - struct DomainMetadata domain = { 1, 0, 1, 0 }; + struct DomainMetadata domain = createDomainMetadata(); for (daqSizeT readCount = 0; readCount < 20; ++readCount) { daqSizeT availableCount = 0; daqReader_getAvailableCount(multireaderAsReader, &availableCount); @@ -143,10 +172,15 @@ void readDataSameRateSignals(daqList* signals) daqReadStatus reportedStatus; daqReaderStatus_getReadStatus(statusAsReaderStatus, &reportedStatus); if (reportedStatus == daqReadStatusEvent) { - handleEvent(status, &domain); + daqErrCode err = handleEvent(status, &domain); - // Buffer size for 400ms worth of samples - bufferSize = (domain.sampleRate * 2) / 5; + if (err == DAQ_SUCCESS) { + // Buffer size for 400ms worth of samples + bufferSize = (domain.sampleRate * 2) / 5; + } + else { + bufferSize = 0; // Allocate nothing + } freeIfAllocated(dataBuffers, signalCount, buffersAllocated); buffersAllocated = allocateBuffers(dataBuffers, signalCount, bufferSize); @@ -174,7 +208,8 @@ void readDataSameRateSignals(daqList* signals) // Calculate tick according to linear data rule. daqInt sampleTick = readStartTick + sample * domain.ruleDelta; - printf("%lld | ", sampleTick); + printAbsoluteTimestamp(&domain, sampleTick); + printf(" | "); for (daqSizeT i = 0; i < signalCount; ++i) { if (dataBuffers[i] == NULL) { continue; @@ -186,7 +221,6 @@ void readDataSameRateSignals(daqList* signals) } } - daqReleaseRef(statusAsReaderStatus); daqReleaseRef(status); Sleep(200); @@ -195,6 +229,7 @@ void readDataSameRateSignals(daqList* signals) freeIfAllocated(dataBuffers, signalCount, buffersAllocated); free(dataBuffers); + freeDomainMetadata(&domain); daqReleaseRef(multireaderAsReader); daqReleaseRef(multireader); } @@ -329,8 +364,7 @@ daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* meta daqDataDescriptor* domainDescriptor = NULL; daqDict_get(parameters, domainDescriptorStr, (daqBaseObject**)&domainDescriptor); - processDataRule(domainDescriptor, metadata); - processReferenceDomainInfo(domainDescriptor, metadata); + err = updateDomainMetadata(domainDescriptor, metadata); daqReleaseRef(domainDescriptorStr); daqReleaseRef(domainDescriptor); @@ -339,10 +373,10 @@ daqErrCode handleEvent(daqMultiReaderStatus* status, struct DomainMetadata* meta return err; } -daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata) +daqErrCode updateDomainMetadata(daqDataDescriptor* descriptor, struct DomainMetadata* metadata) { daqDataRule* dataRule = NULL; - daqDataDescriptor_getRule(domainDataDescriptor, &dataRule); + daqDataDescriptor_getRule(descriptor, &dataRule); if (!checkIsLinearRule(dataRule)) { @@ -351,15 +385,39 @@ daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct Domai return DAQ_ERR_INVALID_DATA; } - // TODO: Check that origin is 1970 + // Origin and unit + daqDataDescriptor_getOrigin(descriptor, &(metadata->origin)); + + daqUnit* unit = NULL; + daqDataDescriptor_getUnit(descriptor, &unit); + daqUnit_getSymbol(unit, &(metadata->unitSymbol)); + daqReleaseRef(unit); + printDaqFormattedString("Origin: %s\n", metadata->origin); + printDaqFormattedString("Unit: %s\n", metadata->unitSymbol); + + // Check if unitSymbo == "s" + daqString* secondsSym = NULL; + daqString_createString(&secondsSym, "s"); + daqBool check = False; + daqBaseObject_equals(metadata->unitSymbol, secondsSym, &check); + daqReleaseRef(secondsSym); + if (!check) { + printf("Unit symbol is not 's'!"); + daqReleaseRef(dataRule); + return DAQ_ERR_INVALID_DATA; + } + + // Sample rate, delta and start daqRatio* ratio = NULL; - daqDataDescriptor_getTickResolution(domainDataDescriptor, &ratio); + daqDataDescriptor_getTickResolution(descriptor, &ratio); + + daqRatio_getNumerator(ratio, &metadata->resNum); + daqRatio_getDenominator(ratio, &metadata->resDen); daqDict* parametersDataRule = NULL; daqDataRule_getParameters(dataRule, ¶metersDataRule); - daqNumber* delta = NULL; getNumberFromDict(parametersDataRule, "delta", &delta); @@ -370,16 +428,19 @@ daqErrCode processDataRule(daqDataDescriptor* domainDataDescriptor, struct Domai daqNumber_getIntValue(delta, &(metadata->ruleDelta)); daqNumber_getIntValue(start, &(metadata->ruleStart)); - daqReleaseRef(delta); daqReleaseRef(start); + daqReleaseRef(delta); daqReleaseRef(parametersDataRule); daqReleaseRef(ratio); daqReleaseRef(dataRule); + // Reference domain info is not mandatory + getReferenceDomainOffset(descriptor, &(metadata->referenceDomainOffset)); + return DAQ_SUCCESS; } -daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, struct DomainMetadata* metadata) +daqErrCode getReferenceDomainOffset(daqDataDescriptor* domainDataDescriptor, daqInt* offset) { daqErrCode err = DAQ_SUCCESS; @@ -388,8 +449,7 @@ daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, s // Reference domain info is not mandatory if (err || domainInfo == NULL) { - printf("Reference domain info unavailable."); - metadata->referenceDomainOffset = 0; + *offset = 0; return DAQ_SUCCESS; } @@ -398,13 +458,12 @@ daqErrCode processReferenceDomainInfo(daqDataDescriptor* domainDataDescriptor, s // Reference domain info is not mandatory if (err || refDomainOffset == NULL) { - printf("Reference domain info unavailable."); - metadata->referenceDomainOffset = 0; + *offset = 0; daqReleaseRef(domainInfo); return DAQ_SUCCESS; } - err = daqInteger_getValue(refDomainOffset, &(metadata->referenceDomainOffset)); + err = daqInteger_getValue(refDomainOffset, offset); daqReleaseRef(refDomainOffset); daqReleaseRef(domainInfo); @@ -428,6 +487,28 @@ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket) return check; } +void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick) +{ + daqInt secondsSinceEpoch = (tick * domain->resNum) / domain->resDen; + + daqInt seconds = secondsSinceEpoch; + daqInt days = secondsSinceEpoch / (60 * 60 * 24); + seconds -= days * (60 * 60 * 24); + + daqInt hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + + daqInt minutes = seconds / 60; + seconds -= minutes * 60; + + daqInt leftoverTicks = tick - (secondsSinceEpoch * domain->resDen) / domain->resNum; + daqInt milliseconds = (1000 * leftoverTicks * domain->resNum) / domain->resDen; + + char* origin = NULL; + daqString_getCharPtr(domain->origin, &origin); + printf("%lldd %lldh %lldmin %lld.%llds since %s", days, hours, minutes, seconds, milliseconds, origin); +} + daqInt getOffsetFromStatus(daqReaderStatus* status) { daqNumber* offsetNum = NULL; @@ -488,4 +569,21 @@ void freeIfAllocated(void** buffers, daqSizeT count, daqBool allocated) free(buffers[i]); buffers[i] = NULL; } +} + +struct DomainMetadata createDomainMetadata() +{ + struct DomainMetadata d = { 1, 0, 1, 0, NULL, NULL }; + return d; +} +void freeDomainMetadata(struct DomainMetadata* p) +{ + if (p->origin != NULL) { + daqReleaseRef(p->origin); + p->origin = NULL; + } + if (p->unitSymbol != NULL) { + daqReleaseRef(p->unitSymbol); + p->unitSymbol = NULL; + } } \ No newline at end of file From 63866297f9188944f3c02293c3fbdf2cf6240d9c Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Fri, 30 Jan 2026 15:53:47 +0100 Subject: [PATCH 09/12] Fix stdout alignment --- examples/multi_reader_read_same_rates.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 2c6749b..7f88906 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -194,7 +194,7 @@ void readDataSameRateSignals(daqList* signals) daqInt readStartTick = domain.ruleStart + domain.referenceDomainOffset + readOffset; printf("\nRead successful (%lld).\nRead %lld samples. Printing samples 0, 1 and %lld.\n", readCount, count, count-1); - printf("-- TIMESTAMP --- | -------- DATA -----------\n"); + printf("--------------- TIMESTAMP ------------------------- | -------- DATA -----------\n"); for (daqSizeT sample = 0; sample < count; ++sample) { // Only print the first and the last samples if (sample == 2) { From 087b546281bd7dbb30ae1059d539f6196c3a3b45 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Mon, 2 Feb 2026 09:11:47 +0100 Subject: [PATCH 10/12] Timestamps with origin 1970/01/01. --- examples/multi_reader_read_same_rates.c | 33 +++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 7f88906..ff71c7c 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -2,6 +2,7 @@ #include #include #include +#include /** * Set of parameters describing the time domain. @@ -85,6 +86,7 @@ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket); * of this format is 1ms, so two samples within a millisecond may have the same time stamp printed out. */ void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick); +daqErrCode getAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick, char* out, size_t out_s); /** * Get offset to the first sample in the read buffer. @@ -209,6 +211,10 @@ void readDataSameRateSignals(daqList* signals) daqInt sampleTick = readStartTick + sample * domain.ruleDelta; printAbsoluteTimestamp(&domain, sampleTick); + printf("\n"); + char timeBuff[64]; + getAbsoluteTimestamp(&domain, sampleTick, timeBuff, sizeof(timeBuff)); + printf("%s", timeBuff); printf(" | "); for (daqSizeT i = 0; i < signalCount; ++i) { if (dataBuffers[i] == NULL) { @@ -501,12 +507,35 @@ void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick) daqInt minutes = seconds / 60; seconds -= minutes * 60; - daqInt leftoverTicks = tick - (secondsSinceEpoch * domain->resDen) / domain->resNum; + daqInt leftoverTicks = tick - ((secondsSinceEpoch * domain->resDen) / domain->resNum); daqInt milliseconds = (1000 * leftoverTicks * domain->resNum) / domain->resDen; char* origin = NULL; daqString_getCharPtr(domain->origin, &origin); - printf("%lldd %lldh %lldmin %lld.%llds since %s", days, hours, minutes, seconds, milliseconds, origin); + printf("%lldd %lldh %lldmin %lld.%03llds since %s", days, hours, minutes, seconds, milliseconds, origin); +} + +daqErrCode getAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick, char* out, size_t out_s) +{ + if (tick < 0) + return DAQ_ERR_INVALIDVALUE; + + daqInt secondsSinceEpoch = (tick * domain->resNum) / domain->resDen; + daqInt leftoverTicks = tick - ((secondsSinceEpoch * domain->resDen) / domain->resNum); + daqInt milliseconds = (1000 * leftoverTicks * domain->resNum) / domain->resDen; + + struct tm time_utc; +#if defined(_MSC_VER) + gmtime_s(&time_utc, &secondsSinceEpoch); +#else + gmtime_r(&secondsSinceEpoch, &time_utc); +#endif + + char tmp[32]; + strftime(tmp, sizeof(tmp), "%Y-%m-%dT%H:%M:%S", &time_utc); + snprintf(out, out_s, "%s.%03lldZ", tmp, milliseconds); + + return DAQ_SUCCESS; } daqInt getOffsetFromStatus(daqReaderStatus* status) From d357eb19846590f51e8ab3b7772de6df1aeb8d30 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Mon, 2 Feb 2026 10:01:13 +0100 Subject: [PATCH 11/12] Support for arbitrary origin. --- examples/multi_reader_read_same_rates.c | 55 ++++-------------- examples/util_headers/daq_time_utils.h | 76 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 45 deletions(-) create mode 100644 examples/util_headers/daq_time_utils.h diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index ff71c7c..7ffef30 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -82,10 +83,9 @@ daqErrCode getReferenceDomainOffset(daqDataDescriptor* domainDataDescriptor, daq daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket); /** -* Print absolute time stamp in format: 20483d 14h 49min 10.934s since 1970-01-01T00:00:00Z. The precision -* of this format is 1ms, so two samples within a millisecond may have the same time stamp printed out. +* Get absolute time stamp in format: 2026-02-02T08:08:35.185Z. The precision of this format is 1ms, so +* two samples within a millisecond may have the same time stamp printed out. */ -void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick); daqErrCode getAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick, char* out, size_t out_s); /** @@ -134,8 +134,6 @@ int main(void) { // addDeviceWrapper(&instance, &device, customString); // getAIChannelSignals(device, &signals); - // TODO: check sampling rates. - // Start reading samples from the signals. readDataSameRateSignals(signals); @@ -196,7 +194,7 @@ void readDataSameRateSignals(daqList* signals) daqInt readStartTick = domain.ruleStart + domain.referenceDomainOffset + readOffset; printf("\nRead successful (%lld).\nRead %lld samples. Printing samples 0, 1 and %lld.\n", readCount, count, count-1); - printf("--------------- TIMESTAMP ------------------------- | -------- DATA -----------\n"); + printf("------ TIMESTAMP ------- | -------- DATA -----------\n"); for (daqSizeT sample = 0; sample < count; ++sample) { // Only print the first and the last samples if (sample == 2) { @@ -210,12 +208,11 @@ void readDataSameRateSignals(daqList* signals) // Calculate tick according to linear data rule. daqInt sampleTick = readStartTick + sample * domain.ruleDelta; - printAbsoluteTimestamp(&domain, sampleTick); - printf("\n"); + // Print formatted timestamp char timeBuff[64]; getAbsoluteTimestamp(&domain, sampleTick, timeBuff, sizeof(timeBuff)); - printf("%s", timeBuff); - printf(" | "); + printf("%s | ", timeBuff); + for (daqSizeT i = 0; i < signalCount; ++i) { if (dataBuffers[i] == NULL) { continue; @@ -398,8 +395,6 @@ daqErrCode updateDomainMetadata(daqDataDescriptor* descriptor, struct DomainMeta daqDataDescriptor_getUnit(descriptor, &unit); daqUnit_getSymbol(unit, &(metadata->unitSymbol)); daqReleaseRef(unit); - printDaqFormattedString("Origin: %s\n", metadata->origin); - printDaqFormattedString("Unit: %s\n", metadata->unitSymbol); // Check if unitSymbo == "s" daqString* secondsSym = NULL; @@ -493,28 +488,6 @@ daqBool isDataDescriptorChangeEvent(daqEventPacket* eventPacket) return check; } -void printAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick) -{ - daqInt secondsSinceEpoch = (tick * domain->resNum) / domain->resDen; - - daqInt seconds = secondsSinceEpoch; - daqInt days = secondsSinceEpoch / (60 * 60 * 24); - seconds -= days * (60 * 60 * 24); - - daqInt hours = seconds / (60 * 60); - seconds -= hours * (60 * 60); - - daqInt minutes = seconds / 60; - seconds -= minutes * 60; - - daqInt leftoverTicks = tick - ((secondsSinceEpoch * domain->resDen) / domain->resNum); - daqInt milliseconds = (1000 * leftoverTicks * domain->resNum) / domain->resDen; - - char* origin = NULL; - daqString_getCharPtr(domain->origin, &origin); - printf("%lldd %lldh %lldmin %lld.%03llds since %s", days, hours, minutes, seconds, milliseconds, origin); -} - daqErrCode getAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick, char* out, size_t out_s) { if (tick < 0) @@ -524,18 +497,10 @@ daqErrCode getAbsoluteTimestamp(struct DomainMetadata* domain, daqInt tick, char daqInt leftoverTicks = tick - ((secondsSinceEpoch * domain->resDen) / domain->resNum); daqInt milliseconds = (1000 * leftoverTicks * domain->resNum) / domain->resDen; - struct tm time_utc; -#if defined(_MSC_VER) - gmtime_s(&time_utc, &secondsSinceEpoch); -#else - gmtime_r(&secondsSinceEpoch, &time_utc); -#endif - - char tmp[32]; - strftime(tmp, sizeof(tmp), "%Y-%m-%dT%H:%M:%S", &time_utc); - snprintf(out, out_s, "%s.%03lldZ", tmp, milliseconds); + char* origin = NULL; + daqString_getCharPtr(domain->origin, &origin); - return DAQ_SUCCESS; + return daq_getAbsoluteTimestampMs(origin, secondsSinceEpoch, milliseconds, out, out_s); } daqInt getOffsetFromStatus(daqReaderStatus* status) diff --git a/examples/util_headers/daq_time_utils.h b/examples/util_headers/daq_time_utils.h new file mode 100644 index 0000000..8ead50b --- /dev/null +++ b/examples/util_headers/daq_time_utils.h @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +/* +* Parse origin string in the format : "YYYY-MM-DDTHH:MM:SSZ" +*/ +static daqErrCode daq_timeOriginToSecondsSince1970(const char* origin, int64_t* out_epoch_sec) +{ + int Y, M, D, h, m, s; + if (!origin) + return DAQ_ERR_INVALID_DATA; + + if (strlen(origin) != 20) return 0; // 1970-01-01T00:00:00Z is 20 chars + if (origin[4] != '-' || origin[7] != '-' || origin[10] != 'T' || + origin[13] != ':' || origin[16] != ':' || origin[19] != 'Z') + return DAQ_ERR_INVALID_DATA; + + if (sscanf(origin, "%4d-%2d-%2dT%2d:%2d:%2dZ", &Y, &M, &D, &h, &m, &s) != 6) + return DAQ_ERR_INVALID_DATA; + + struct tm t = { 0 }; + t.tm_year = Y - 1900; + t.tm_mon = M - 1; + t.tm_mday = D; + t.tm_hour = h; + t.tm_min = m; + t.tm_sec = s; + + // tm structo to seconds +#if defined(_MSC_VER) + *out_epoch_sec = (daqInt)_mkgmtime(&t); +#else + *out_epoch_sec = (daqInt)timegm(&t); +#endif + return DAQ_SUCCESS; +} + +/** +* Convert seconds and milliseconds since 1970 to a formatted timestamp. +*/ +static void daq_secondsSince1970ToTimestamp(daqInt seconds, daqInt milliSeconds, char* out, size_t out_sz) +{ + struct tm tm_utc; +#if defined(_MSC_VER) + gmtime_s(&tm_utc, &seconds); +#else + gmtime_r(&sec, &tm_utc); +#endif + + char tmp[32]; + strftime(tmp, sizeof(tmp), "%Y-%m-%dT%H:%M:%S", &tm_utc); + snprintf(out, out_sz, "%s.%03lldZ", tmp, milliSeconds); +} + +/** +* Get absolute time stamp in ms precision from origin, seconds since origin and remaining milliseconds. +* origin_iso8601 is in format "YYYY-MM-DDTHH:MM:SSZ" in UTC. delta_sec is seconds since origin. +*/ +static daqErrCode daq_getAbsoluteTimestampMs(const char* originIso8601, daqInt seconds, daqInt milliSeconds, char* out, size_t out_sz) +{ + daqErrCode err = DAQ_SUCCESS; + + daqInt originSeconds = 0; + err = daq_timeOriginToSecondsSince1970(originIso8601, &originSeconds); + if (err != DAQ_SUCCESS) + return err; + + // Add origin + delta in microseconds. + // (delta_us can be large; this is still safe as long as you stay within int64 range) + daqInt totalSeconds = originSeconds + seconds; + + daq_secondsSince1970ToTimestamp(totalSeconds, milliSeconds, out, out_sz); + return err; +} \ No newline at end of file From 12197e2dc2a437e64e914e74a5edb27da3b31d00 Mon Sep 17 00:00:00 2001 From: Tomaz Cvetko Date: Tue, 3 Feb 2026 13:53:06 +0100 Subject: [PATCH 12/12] Check that sample rates match. --- examples/multi_reader_read_same_rates.c | 76 ++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/examples/multi_reader_read_same_rates.c b/examples/multi_reader_read_same_rates.c index 7ffef30..82829d9 100644 --- a/examples/multi_reader_read_same_rates.c +++ b/examples/multi_reader_read_same_rates.c @@ -56,6 +56,11 @@ daqErrCode addDeviceWrapper(daqInstance** instance, daqDevice** device, const ch */ void getAIChannelSignals(daqDevice* device, daqList** signals); +/** +* Check if all signals have the same sampling rate. +*/ +daqErrCode checkSamplingRates(daqList* signals, daqSizeT* out_rate); + /** * Create multi reader from a list of signals. */ @@ -134,8 +139,13 @@ int main(void) { // addDeviceWrapper(&instance, &device, customString); // getAIChannelSignals(device, &signals); + daqSizeT sampleRate = 0; + daqErrCode err = checkSamplingRates(signals, &sampleRate); + // Start reading samples from the signals. - readDataSameRateSignals(signals); + if (err == DAQ_SUCCESS) + printf("Signals have matching sample rate of %lld Hz.\n", sampleRate); + readDataSameRateSignals(signals); daqReleaseRef(signals); daqReleaseRef(device); @@ -311,6 +321,70 @@ void getAIChannelSignals(daqDevice* device, daqList** signals) daqReleaseRef(filter); } +daqErrCode checkSamplingRates(daqList* signals, daqSizeT* out_rate) +{ + daqErrCode err = DAQ_SUCCESS; + + daqBool sampleRateUninitialized = True; + daqSizeT sampleRate = 0; + + daqSizeT signalCount = 0; + daqList_getCount(signals, &signalCount); + for (daqSizeT i = 0; i < signalCount; ++i) { + daqSignal* signal = NULL; + daqList_getItemAt(signals, i, &signal); + if (signal == NULL) { + err = DAQ_ERR_GENERALERROR; + break; + } + + daqSignal* domainSignal = NULL; + daqSignal_getDomainSignal(signal, &domainSignal); + if (domainSignal == NULL) { + err = DAQ_ERR_GENERALERROR; + daqReleaseRef(signal); + break; + } + + daqDataDescriptor* descriptor = NULL; + daqSignal_getDescriptor(domainSignal, &descriptor); + + if (descriptor == NULL) { + err = DAQ_ERR_GENERALERROR; + daqReleaseRef(domainSignal); + daqReleaseRef(signal); + break; + } + + daqSizeT rate = 0; + daqErrCode status = getSampleRate(&rate, descriptor); + + if (status != DAQ_SUCCESS) { + err = DAQ_ERR_GENERALERROR; + daqReleaseRef(descriptor); + daqReleaseRef(domainSignal); + daqReleaseRef(signal); + break; + } + if (sampleRateUninitialized) { + sampleRate = rate; + sampleRateUninitialized = False; + } + + if (sampleRate != rate) { + err = DAQ_ERR_GENERALERROR; + } + + daqReleaseRef(descriptor); + daqReleaseRef(domainSignal); + daqReleaseRef(signal); + if (err != DAQ_SUCCESS) + break; + } + *out_rate = sampleRate; + return err; +} + daqErrCode createMultiReader(daqList* signals, daqMultiReader** reader) { daqErrCode err = DAQ_SUCCESS;