DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Loading...
Searching...
No Matches
HardwareAdapterNIDAQ.cpp
Go to the documentation of this file.
1// This file is part of DynExp.
2
3#include "stdafx.h"
5
6namespace DynExpHardware
7{
17
22
32
34 {
36 { "Trigger disabled (start immediately)", TriggerModeType::Disabled },
37 { "Trigger on rising edge", TriggerModeType::RisingEdge },
38 { "Trigger on falling edge", TriggerModeType::FallingEdge }
39 };
40
41 return List;
42 }
43
45 {
46 // Get required buffer size.
47 auto RequiredSize = NIDAQSyms::DAQmxGetSysDevNames(nullptr, 0);
48 if (RequiredSize < 0)
49 throw NIDAQException("Error obtaining buffer size for enumerating NIDAQmx devices.", RequiredSize);
50
51 std::string DeviceList;
52 DeviceList.resize(RequiredSize);
53
54 // Since C++17, writing to std::string's internal buffer is allowed.
55 auto Result = NIDAQSyms::DAQmxGetSysDevNames(DeviceList.data(), Util::NumToT<NIDAQSyms::uInt32>(DeviceList.size()));
56 if (Result < 0)
57 throw NIDAQException("Error enumerating NIDAQmx devices.", Result);
58 DeviceList = Util::TrimTrailingZeros(DeviceList);
59
60 // All descriptors are separated by ','. Replace that by ' ' and use std::istringstream to split by ' '.
61 std::replace(DeviceList.begin(), DeviceList.end(), ',', ' ');
62 std::istringstream ss(DeviceList);
63 std::vector<std::string> DeviceDescriptors{ std::istream_iterator<std::string>(ss), std::istream_iterator<std::string>() };
64
65 return DeviceDescriptors;
66 }
67
69 : Buffer(BufferSize), Stream(&Buffer)
70 {
71 Stream.exceptions(std::iostream::failbit | std::iostream::badbit);
72 }
73
75 {
76 Stream.clear();
77 Buffer.clear();
78 }
79
86
88 {
89 if (NITask)
90 {
91 // Ignore errors here since they cannot be handled properly (tasks should always be clearable).
92 NIDAQSyms::DAQmxStopTask(NITask);
93 NIDAQSyms::DAQmxClearTask(NITask);
94 }
95 }
96
97 void NIDAQTask::AddChannel(ChannelHandleType ChannelHandle, uint64_t NumSamples)
98 {
99 ChannelIndexMap[ChannelHandle] = NumChannels++;
100 this->NumSamples = NumSamples;
101
102 if (!IsCombined())
103 return;
104
107 else
109
111 {
112 ReadStreamPerChannel.clear();
113 for (decltype(NumChannels) i = 0; i < NumChannels; ++i)
114 ReadStreamPerChannel.emplace_back(std::make_unique<CircularStream>(GetSampleSizeInBytes() * NumSamples));
115 }
116 }
117
118 NIDAQHardwareAdapter::NIDAQHardwareAdapter(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType&& Params)
119 : HardwareAdapterBase(OwnerThreadID, std::move(Params))
120 {
121 }
122
124 {
125 // Nothing to do (refer to IsConnectedChild()).
126 // NIDAQTask objects will be destroyed when Tasks is destroyed.
127 }
128
130 double Timeout) const
131 {
133
135 auto Task = GetTaskUnsafe(Handle);
136
137 auto Result = NIDAQSyms::DAQmxCreateDIChan(Task->NITask, ChannelName.data(), "", DAQmx_Val_ChanPerLine);
138 CheckError(Result);
139
140 {
141 auto DerivedParams = dynamic_Params_cast<NIDAQHardwareAdapter>(GetParams());
142 Task->AddChannel(Handle, DerivedParams->StreamSizeParams.StreamSize);
143
144 Result = NIDAQSyms::DAQmxSetReadReadAllAvailSamp(Task->NITask, true);
145 CheckError(Result);
146 Result = NIDAQSyms::DAQmxSetReadOverWrite(Task->NITask, DAQmx_Val_OverwriteUnreadSamps);
147 CheckError(Result);
148
149 InitializeTaskTimingUnsafe(Task, Timeout, DerivedParams->NumericSampleStreamParams.SamplingRate,
150 DerivedParams->NumericSampleStreamParams.SamplingMode);
151 InitializeTriggerUnsafe(Task, DerivedParams->TriggerMode, DerivedParams->TriggerChannel.Get());
152 } // DerivedParams unlocked here.
153
154 StartTaskUnsafe(Task);
155
156 return Handle;
157 }
158
160 NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType UseOnlyOnBrdMem, double Timeout) const
161 {
163
165 auto Task = GetTaskUnsafe(Handle);
166
167 auto Result = NIDAQSyms::DAQmxCreateDOChan(Task->NITask, ChannelName.data(), "", DAQmx_Val_ChanPerLine);
168 CheckError(Result);
169
170 auto DerivedParams = dynamic_Params_cast<NIDAQHardwareAdapter>(GetParams());
171 Task->AddChannel(Handle, DerivedParams->StreamSizeParams.StreamSize);
172
173 if (DerivedParams->StreamSizeParams.StreamSize > 1)
174 {
175 Result = NIDAQSyms::DAQmxSetBufOutputBufSize(Task->NITask, DerivedParams->StreamSizeParams.StreamSize);
176 CheckError(Result);
177 Result = NIDAQSyms::DAQmxSetWriteRelativeTo(Task->NITask, DAQmx_Val_FirstSample);
178 CheckError(Result);
179 }
180
181 Result = NIDAQSyms::DAQmxSetDOUseOnlyOnBrdMem(Task->NITask, ChannelName.data(),
183 CheckError(Result);
184
185 InitializeTaskTimingUnsafe(Task, Timeout, DerivedParams->NumericSampleStreamParams.SamplingRate,
186 DerivedParams->NumericSampleStreamParams.SamplingMode);
187 InitializeTriggerUnsafe(Task, DerivedParams->TriggerMode, DerivedParams->TriggerChannel.Get());
188
189 if (DerivedParams->StreamSizeParams.StreamSize <= 1)
190 StartTaskUnsafe(Task);
191
192 return Handle;
193 }
194
196 double MinValue, double MaxValue, double Timeout, int32_t TerminalConfig) const
197 {
199
201 auto Task = GetTaskUnsafe(Handle);
202
203 auto Result = NIDAQSyms::DAQmxCreateAIVoltageChan(Task->NITask, ChannelName.data(), "", TerminalConfig,
204 MinValue, MaxValue, DAQmx_Val_Volts, nullptr);
205 CheckError(Result);
206
207 {
208 auto DerivedParams = dynamic_Params_cast<NIDAQHardwareAdapter>(GetParams());
209 Task->AddChannel(Handle, DerivedParams->StreamSizeParams.StreamSize);
210
211 Result = NIDAQSyms::DAQmxSetReadReadAllAvailSamp(Task->NITask, true);
212 CheckError(Result);
213 Result = NIDAQSyms::DAQmxSetReadOverWrite(Task->NITask, DAQmx_Val_OverwriteUnreadSamps);
214 CheckError(Result);
215
216 InitializeTaskTimingUnsafe(Task, Timeout, DerivedParams->NumericSampleStreamParams.SamplingRate,
217 DerivedParams->NumericSampleStreamParams.SamplingMode);
218 InitializeTriggerUnsafe(Task, DerivedParams->TriggerMode, DerivedParams->TriggerChannel.Get());
219 } // DerivedParams unlocked here.
220
221 StartTaskUnsafe(Task);
222
223 return Handle;
224 }
225
227 double MinValue, double MaxValue, NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType UseOnlyOnBrdMem, double Timeout) const
228 {
230
232 auto Task = GetTaskUnsafe(Handle);
233
234 auto Result = NIDAQSyms::DAQmxCreateAOVoltageChan(Task->NITask, ChannelName.data(), "",
235 MinValue, MaxValue, DAQmx_Val_Volts, nullptr);
236 CheckError(Result);
237
238 auto DerivedParams = dynamic_Params_cast<NIDAQHardwareAdapter>(GetParams());
239 Task->AddChannel(Handle, DerivedParams->StreamSizeParams.StreamSize);
240
241 if (DerivedParams->StreamSizeParams.StreamSize > 1)
242 {
243 Result = NIDAQSyms::DAQmxSetBufOutputBufSize(Task->NITask, DerivedParams->StreamSizeParams.StreamSize);
244 CheckError(Result);
245 Result = NIDAQSyms::DAQmxSetWriteRelativeTo(Task->NITask, DAQmx_Val_FirstSample);
246 CheckError(Result);
247 }
248
249 Result = NIDAQSyms::DAQmxSetAOUseOnlyOnBrdMem(Task->NITask, ChannelName.data(),
251 CheckError(Result);
252
253 InitializeTaskTimingUnsafe(Task, Timeout, DerivedParams->NumericSampleStreamParams.SamplingRate,
254 DerivedParams->NumericSampleStreamParams.SamplingMode);
255 InitializeTriggerUnsafe(Task, DerivedParams->TriggerMode, DerivedParams->TriggerChannel.Get());
256
257 if (DerivedParams->StreamSizeParams.StreamSize <= 1)
258 StartTaskUnsafe(Task);
259
260 return Handle;
261 }
262
264 {
266
267 return RemoveTaskUnsafe(ChannelHandle);
268 }
269
270 std::vector<NIDAQTask::DigitalValueType> NIDAQHardwareAdapter::ReadDigitalValues(ChannelHandleType ChannelHandle) const
271 {
273
274 auto Task = GetTaskUnsafe(ChannelHandle);
275 if (Task->GetNumSamples() > std::numeric_limits<NIDAQSyms::int32>::max())
276 ThrowExceptionUnsafe(std::make_exception_ptr(Util::OverflowException(
277 "Number of samples to read must not exceed " + std::to_string(std::numeric_limits<NIDAQSyms::int32>::max()) + ".")));
278
279 // Restart if finished
280 NIDAQSyms::uInt64 Position{};
281 auto Result = NIDAQSyms::DAQmxGetReadCurrReadPos(Task->NITask, &Position);
282 CheckError(Result);
283 if (Position == Task->GetNumSamples() && HasFinishedTaskUnsafe(Task))
284 RestartTaskUnsafe(Task);
285
286 std::vector<NIDAQTask::DigitalValueType> Data;
287 Data.resize(Task->GetBufferSizeInSamples(), 0);
288
289 NIDAQSyms::int32 NumSamplesRead = 0;
290 NIDAQSyms::int32 BytesPerSample = 0;
291 Result = NIDAQSyms::DAQmxReadDigitalLines(Task->NITask, DAQmx_Val_Auto, Task->GetTimeout(), DAQmx_Val_GroupByChannel,
292 Data.data(), Util::NumToT<NIDAQSyms::uInt32>(Task->GetBufferSizeInSamples() * Task->GetSampleSizeInBytes()),
293 &NumSamplesRead, &BytesPerSample, nullptr);
294 if (Result != DAQmxErrorOperationTimedOut) // Ignore spurious timeout erros. Returns an empty vector if this error occurs.
295 CheckReadError(Task, Result);
296
297 // BytesPerSample is the amount of bytes that one channel consists of.
298 if (BytesPerSample != Task->GetSampleSizeInBytes())
299 ThrowExceptionUnsafe(std::make_exception_ptr(Util::InvalidDataException(
300 "Received data from DAQmxReadDigitalLines() which does not correspond to the expected memory layout.")));
301
302 if (!Task->IsCombined())
303 Data.resize(NumSamplesRead);
304 else
305 {
306 for (decltype(Task->NumChannels) i = 0; i < Task->NumChannels; ++i)
307 Task->ReadStreamPerChannel[i]->Stream.write(reinterpret_cast<const char*>(Data.data() + i * NumSamplesRead), NumSamplesRead * Task->GetSampleSizeInBytes());
308
309 const auto NumBytesToRead = Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Buffer.gsize();
310 Data.clear();
311 Data.resize(NumBytesToRead / Task->GetSampleSizeInBytes());
312 Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Stream.read(reinterpret_cast<char*>(Data.data()), NumBytesToRead);
313 Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Clear();
314 }
315
316 return Data;
317 }
318
319 // Returns number of samples successfully written.
320 int32_t NIDAQHardwareAdapter::WriteDigitalValues(ChannelHandleType ChannelHandle, const std::vector<NIDAQTask::DigitalValueType>& Values) const
321 {
322 if (Values.empty())
323 return 0;
324
326
327 auto Task = GetTaskUnsafe(ChannelHandle);
328 if (Values.size() > std::numeric_limits<NIDAQSyms::int32>::max())
329 ThrowExceptionUnsafe(std::make_exception_ptr(Util::OverflowException(
330 "Number of samples to write must not exceed " + std::to_string(std::numeric_limits<NIDAQSyms::int32>::max()) + ".")));
331
332 NIDAQSyms::int32 NumSamplesWritten = 0;
333 if (!Task->IsCombined())
334 {
335 auto Result = NIDAQSyms::DAQmxWriteDigitalLines(Task->NITask, Util::NumToT<NIDAQSyms::int32>(Values.size()), false, Task->GetTimeout(),
336 DAQmx_Val_GroupByChannel, Values.data(), &NumSamplesWritten, nullptr);
337 CheckError(Result);
338 }
339 else
340 {
341 auto Destiny = Task->DigitalValues | std::views::drop(Task->GetChannelIndex(ChannelHandle) * Task->GetNumSamples());
342 auto NewValues = Values | std::views::take(Task->GetNumSamples());
343 std::ranges::copy(NewValues, Destiny.begin());
344 if (NewValues.size() < Task->GetNumSamples())
345 std::ranges::fill_n(Destiny.begin() + NewValues.size(), Task->GetNumSamples() - NewValues.size(), 0);
346
347 auto Result = NIDAQSyms::DAQmxWriteDigitalLines(Task->NITask, Util::NumToT<NIDAQSyms::int32>(Task->GetNumSamples()), false, Task->GetTimeout(),
348 DAQmx_Val_GroupByChannel, Task->DigitalValues.data(), &NumSamplesWritten, nullptr);
349 CheckError(Result);
350 }
351
352 return NumSamplesWritten;
353 }
354
355 std::vector<NIDAQTask::AnalogValueType> NIDAQHardwareAdapter::ReadAnalogValues(ChannelHandleType ChannelHandle) const
356 {
358
359 auto Task = GetTaskUnsafe(ChannelHandle);
360 if (Task->GetNumSamples() > std::numeric_limits<NIDAQSyms::int32>::max())
361 ThrowExceptionUnsafe(std::make_exception_ptr(Util::OverflowException(
362 "Number of samples to read must not exceed " + std::to_string(std::numeric_limits<NIDAQSyms::int32>::max()) + ".")));
363
364 // Restart if finished
365 NIDAQSyms::uInt64 Position{};
366 auto Result = NIDAQSyms::DAQmxGetReadCurrReadPos(Task->NITask, &Position);
367 CheckError(Result);
368 if (Position == Task->GetNumSamples() && HasFinishedTaskUnsafe(Task))
369 RestartTaskUnsafe(Task);
370
371 std::vector<NIDAQTask::AnalogValueType> Data;
372 Data.resize(Task->GetBufferSizeInSamples(), .0);
373
374 NIDAQSyms::int32 NumSamplesRead = 0;
375 Result = NIDAQSyms::DAQmxReadAnalogF64(Task->NITask, DAQmx_Val_Auto, Task->GetTimeout(), DAQmx_Val_GroupByChannel,
376 Data.data(), Util::NumToT<NIDAQSyms::uInt32>(Task->GetNumSamples()), &NumSamplesRead, nullptr);
377 if (Result != DAQmxErrorOperationTimedOut) // Ignore spurious timeout erros. Returns an empty vector if this error occurs.
378 CheckReadError(Task, Result);
379
380 if (!Task->IsCombined())
381 Data.resize(NumSamplesRead);
382 else
383 {
384 for (decltype(Task->NumChannels) i = 0; i < Task->NumChannels; ++i)
385 Task->ReadStreamPerChannel[i]->Stream.write(reinterpret_cast<const char*>(Data.data() + i * NumSamplesRead), NumSamplesRead * Task->GetSampleSizeInBytes());
386
387 const auto NumBytesToRead = Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Buffer.gsize();
388 Data.clear();
389 Data.resize(NumBytesToRead / Task->GetSampleSizeInBytes());
390 Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Stream.read(reinterpret_cast<char*>(Data.data()), NumBytesToRead);
391 Task->ReadStreamPerChannel[Task->GetChannelIndex(ChannelHandle)]->Clear();
392 }
393
394 return Data;
395 }
396
397 // Returns number of samples successfully written.
398 int32_t NIDAQHardwareAdapter::WriteAnalogValues(ChannelHandleType ChannelHandle, const std::vector<NIDAQTask::AnalogValueType>& Values) const
399 {
400 if (Values.empty())
401 return 0;
402
404
405 auto Task = GetTaskUnsafe(ChannelHandle);
406 if (Values.size() > std::numeric_limits<NIDAQSyms::int32>::max())
407 ThrowExceptionUnsafe(std::make_exception_ptr(Util::OverflowException(
408 "Number of samples to write must not exceed " + std::to_string(std::numeric_limits<NIDAQSyms::int32>::max()) + ".")));
409
410 NIDAQSyms::int32 NumSamplesWritten = 0;
411 if (!Task->IsCombined())
412 {
413 auto Result = NIDAQSyms::DAQmxWriteAnalogF64(Task->NITask, Util::NumToT<NIDAQSyms::int32>(Values.size()), false, Task->GetTimeout(),
414 DAQmx_Val_GroupByChannel, Values.data(), &NumSamplesWritten, nullptr);
415 CheckError(Result);
416 }
417 else
418 {
419 auto Destiny = Task->AnalogValues | std::views::drop(Task->GetChannelIndex(ChannelHandle) * Task->GetNumSamples());
420 auto NewValues = Values | std::views::take(Task->GetNumSamples());
421 std::ranges::copy(NewValues, Destiny.begin());
422 if (NewValues.size() < Task->GetNumSamples())
423 std::ranges::fill_n(Destiny.begin() + NewValues.size(), Task->GetNumSamples() - NewValues.size(), .0);
424
425 auto Result = NIDAQSyms::DAQmxWriteAnalogF64(Task->NITask, Util::NumToT<NIDAQSyms::int32>(Task->GetNumSamples()), false, Task->GetTimeout(),
426 DAQmx_Val_GroupByChannel, Task->AnalogValues.data(), &NumSamplesWritten, nullptr);
427 CheckError(Result);
428 }
429
430 return NumSamplesWritten;
431 }
432
434 {
436
437 StartTaskUnsafe(GetTaskUnsafe(ChannelHandle));
438 }
439
441 {
443
444 StopTaskUnsafe(GetTaskUnsafe(ChannelHandle));
445 }
446
448 {
450
451 RestartTaskUnsafe(GetTaskUnsafe(ChannelHandle));
452 }
453
455 {
457
458 return HasFinishedTaskUnsafe(GetTaskUnsafe(ChannelHandle));
459 }
460
462 {
464
465 return GetTaskUnsafe(ChannelHandle);
466 }
467
469 {
470 // auto lock = AcquireLock(); not necessary here, since DynExp ensures that Object::Reset() can only
471 // be called if respective object is not in use.
472
473 // NIDAQTask objects are destroyed here.
474 Tasks.clear();
475
477 }
478
480 {
481 // Nothing to do (refer to IsConnectedChild()). Always ready ;)
482 }
483
485 {
487
488 auto Exception = GetExceptionUnsafe();
489 Util::ForwardException(Exception);
490
491 return true;
492 }
493
495 {
496 // This is always the case since all work is done based on tasks. Those tasks itself refer
497 // to a specific device. Tasks may refer to distinct devices. Thus, this class just provides
498 // functionality to create the tasks. It does not manage physical NIDAQ devices itself.
499 return true;
500 }
501
502 void NIDAQHardwareAdapter::CheckError(const int32_t Result, const std::source_location Location) const
503 {
504 if (Result == 0)
505 return;
506
507 uint32_t RequiredSize = NIDAQSyms::DAQmxGetExtendedErrorInfo(nullptr, 0);
508 std::string ErrorString(RequiredSize, '\0');
509 NIDAQSyms::DAQmxGetExtendedErrorInfo(ErrorString.data(), RequiredSize);
510
511 if (Result < 0)
512 {
513 // AcquireLock() has already been called by an (in)direct caller of this function.
514 ThrowExceptionUnsafe(std::make_exception_ptr(NIDAQException(ErrorString, Result, Util::ErrorType::Error, Location)));
515 }
516 else
517 {
518 // Only a warning has occurred.
519 Util::EventLogger().Log(NIDAQException(ErrorString, Result, Util::ErrorType::Warning, Location));
520 }
521 }
522
523 void NIDAQHardwareAdapter::CheckReadError(NIDAQTask* Task, const int32_t Result, const std::source_location Location) const
524 {
525 // If there are too many samples to be read at once, restart the task in order to clear the input buffer.
526 if (Result == DAQmxErrorSamplesNoLongerAvailable)
527 RestartTaskUnsafe(Task);
528 else
529 {
530 // It is not an error if not all samples have been acquired yet.
531 if (Result != DAQmxErrorSamplesNotYetAvailable)
532 CheckError(Result, Location);
533 }
534 }
535
536 NIDAQSyms::TaskHandle NIDAQHardwareAdapter::CreateTaskUnsafe() const
537 {
538 NIDAQSyms::TaskHandle Handle = nullptr;
539
540 auto Result = NIDAQSyms::DAQmxCreateTask("", &Handle);
541 CheckError(Result);
542 if (!Handle)
543 ThrowExceptionUnsafe(std::make_exception_ptr(Util::InvalidDataException(
544 "Unexpected error creating an NIDAQ task: nullptr received from DAQmxCreateTask().")));
545
546 return Handle;
547 }
548
550 double Timeout, double SamplingRate, DynExpInstr::NumericSampleStreamParamsExtension::SamplingModeType SamplingMode) const
551 {
552 if (Task->GetNumSamples() > 1)
553 {
556
557 auto Result = NIDAQSyms::DAQmxCfgSampClkTiming(Task->NITask, "", SamplingRate, DAQmx_Val_Rising,
558 NIDAQSamplingMode == NIDAQTask::SamplingModeType::Single ? DAQmx_Val_FiniteSamps : DAQmx_Val_ContSamps, Task->GetNumSamples());
559 CheckError(Result);
560
561 Task->Timeout = Timeout;
562 Task->SamplingRate = SamplingRate;
563 Task->SamplingMode = NIDAQSamplingMode;
564 }
565 }
566
567 void NIDAQHardwareAdapter::InitializeTriggerUnsafe(NIDAQTask* Task, NIDAQHardwareAdapterParams::TriggerModeType TriggerMode, std::string_view TriggerChannelName) const
568 {
570 return;
571
572 auto Result = NIDAQSyms::DAQmxCfgDigEdgeStartTrig(Task->NITask, TriggerChannelName.data(),
573 TriggerMode == NIDAQHardwareAdapterParams::TriggerModeType::RisingEdge ? DAQmx_Val_Rising : DAQmx_Val_Falling);
574 CheckError(Result);
575 }
576
578 {
579 std::ranges::for_each(Task->ReadStreamPerChannel, [](auto& Stream) { Stream->Clear(); });
580
581 auto Result = NIDAQSyms::DAQmxStartTask(Task->NITask);
582 CheckError(Result);
583 }
584
586 {
587 auto Result = NIDAQSyms::DAQmxStopTask(Task->NITask);
588 CheckError(Result);
589 }
590
592 {
593 StopTaskUnsafe(Task);
594 StartTaskUnsafe(Task);
595 }
596
598 {
599 NIDAQSyms::bool32 IsDone{};
600 auto Result = NIDAQSyms::DAQmxIsTaskDone(Task->NITask, &IsDone);
601 CheckError(Result);
602
603 return IsDone;
604 }
605
607 {
608 return std::hash<std::string_view>()(ChannelName);
609 }
610
612 {
613 return Tasks.find(ChannelHandle) != Tasks.cend();
614 }
615
616 bool NIDAQHardwareAdapter::TaskExistsUnsafe(std::string_view ChannelName) const
617 {
618 return TaskExistsUnsafe(ChannelNameToChannelHandle(ChannelName));
619 }
620
622 {
623 auto Task = Tasks.find(ChannelHandle);
624 if (Task == Tasks.cend())
625 ThrowExceptionUnsafe(std::make_exception_ptr(Util::NotFoundException(
626 "The specified ChannelHandle does not exist.")));
627
628 return Task->second.get();
629 }
630
631 NIDAQTask* NIDAQHardwareAdapter::GetTaskUnsafe(std::string_view ChannelName) const
632 {
633 return GetTaskUnsafe(ChannelNameToChannelHandle(ChannelName));
634 }
635
636 NIDAQHardwareAdapter::ChannelHandleType NIDAQHardwareAdapter::InsertTaskUnsafe(std::string_view ChannelName, std::shared_ptr<NIDAQTask>&& TaskHandle) const
637 {
638 auto Handle = ChannelNameToChannelHandle(ChannelName);
639
640 if (TaskExistsUnsafe(Handle))
642 "There is already a task for channel " + std::string(ChannelName) + ".")));
643
644 try
645 {
646 Tasks.emplace(std::make_pair(Handle, std::move(TaskHandle)));
647 }
648 catch (...)
649 {
650 ThrowExceptionUnsafe(std::current_exception());
651 }
652
653 return Handle;
654 }
655
657 {
658 auto DerivedParams = dynamic_Params_cast<NIDAQHardwareAdapter>(GetParams());
659
660 if (DerivedParams->ChannelMode == NIDAQHardwareAdapterParams::ChannelModeType::CombineChannels && DerivedParams->StreamSizeParams.StreamSize > 1)
661 {
662 if (Tasks.empty())
663 return InsertTaskUnsafe(ChannelName, std::make_shared<NIDAQTask>(Type, CreateTaskUnsafe(), NIDAQHardwareAdapterParams::ChannelModeType::CombineChannels));
664 else
665 {
666 // Intended copy of shared_ptr.
667 auto Task = Tasks.begin()->second;
668 if (Task->GetType() != Type)
669 ThrowExceptionUnsafe(std::make_exception_ptr(Util::InvalidArgException(
670 "Only channels of the same type can be combined into one task.")));
671
672 StopTaskUnsafe(Task.get());
673
674 return InsertTaskUnsafe(ChannelName, std::move(Task));
675 }
676 }
677 else
678 {
679 return InsertTaskUnsafe(ChannelName, std::make_shared<NIDAQTask>(Type, CreateTaskUnsafe()));
680 }
681 }
682
684 {
685 // Does nothing if respective task does not exist and returns true if a taks has been removed.
686 // For multiple channels combined into one task, removing one channel does only remove the
687 // corresponding task if no further channels relate to that task. This is the case since
688 // NIDAQmx does not support removing channels from tasks.
689 return Tasks.erase(ChannelHandle) > 0;
690 }
691}
Implementation of a hardware adapter to control National Instruments NIDAQmx hardware.
static Util::TextValueListType< ChannelModeType > ChannelModeTypeStrList()
static Util::TextValueListType< TriggerModeType > TriggerModeTypeStrList()
ChannelHandleType InitializeDigitalOutChannel(std::string_view ChannelName, NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType UseOnlyOnBrdMem, double Timeout=0) const
void EnsureReadyStateChild() override final
Ensures that this Object instance is ready by possibly starting its worker thread or by opening conne...
void StartTask(ChannelHandleType ChannelHandle) const
void StartTaskUnsafe(NIDAQTask *Task) const
bool DeregisterChannel(ChannelHandleType ChannelHandle) const
bool HasFinishedTask(ChannelHandleType ChannelHandle) const
void InitializeTaskTimingUnsafe(NIDAQTask *Task, double Timeout, double SamplingRate, DynExpInstr::NumericSampleStreamParamsExtension::SamplingModeType SamplingMode) const
int32_t WriteAnalogValues(ChannelHandleType ChannelHandle, const std::vector< NIDAQTask::AnalogValueType > &Values) const
ChannelHandleType InitializeAnalogInChannel(std::string_view ChannelName, double MinValue, double MaxValue, double Timeout=0, int32_t TerminalConfig=DAQmx_Val_RSE) const
bool TaskExistsUnsafe(ChannelHandleType ChannelHandle) const
void CheckReadError(NIDAQTask *Task, const int32_t Result, const std::source_location Location=std::source_location::current()) const
ChannelHandleType CreateTaskIfNotExistsUnsafe(std::string_view ChannelName, NIDAQTask::ChannelType Type) const
void StopTask(ChannelHandleType ChannelHandle) const
bool IsConnectedChild() const noexcept override final
Determines the connection status of the hardware interface.
ChannelHandleType InsertTaskUnsafe(std::string_view ChannelName, std::shared_ptr< NIDAQTask > &&TaskHandle) const
ChannelHandleType InitializeAnalogOutChannel(std::string_view ChannelName, double MinValue, double MaxValue, NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType UseOnlyOnBrdMem, double Timeout=0) const
NIDAQTask::ChannelHandleType ChannelHandleType
bool HasFinishedTaskUnsafe(NIDAQTask *Task) const
NIDAQTask * GetTaskUnsafe(ChannelHandleType ChannelHandle) const
NIDAQSyms::TaskHandle CreateTaskUnsafe() const
void RestartTaskUnsafe(NIDAQTask *Task) const
const NIDAQTask * GetTask(ChannelHandleType ChannelHandle) const
std::vector< NIDAQTask::DigitalValueType > ReadDigitalValues(ChannelHandleType ChannelHandle) const
void InitializeTriggerUnsafe(NIDAQTask *Task, NIDAQHardwareAdapterParams::TriggerModeType TriggerMode, std::string_view TriggerChannelName) const
ChannelHandleType InitializeDigitalInChannel(std::string_view ChannelName, double Timeout=0) const
bool IsReadyChild() const override final
Returns wheter this Object instance is ready (e.g. it is running or connected to a hardware device) a...
void RestartTask(ChannelHandleType ChannelHandle) const
NIDAQHardwareAdapter(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType &&Params)
ChannelHandleType ChannelNameToChannelHandle(std::string_view ChannelName) const
void ResetImpl(dispatch_tag< HardwareAdapterBase >) override final
bool RemoveTaskUnsafe(ChannelHandleType ChannelHandle) const
int32_t WriteDigitalValues(ChannelHandleType ChannelHandle, const std::vector< NIDAQTask::DigitalValueType > &Values) const
std::vector< NIDAQTask::AnalogValueType > ReadAnalogValues(ChannelHandleType ChannelHandle) const
void CheckError(const int32_t Result, const std::source_location Location=std::source_location::current()) const
DynExp::ParamsBase::Param< UseOnlyOnBrdMemType > UseOnlyOnBrdMem
Write directly to device's onboard memory?
static Util::TextValueListType< UseOnlyOnBrdMemType > UseOnlyOnBrdMemTypeStrList()
bool IsCombined() const noexcept
std::vector< AnalogValueType > AnalogValues
NIDAQTask(ChannelType Type, NIDAQSyms::TaskHandle NITask, NIDAQHardwareAdapterParams::ChannelModeType ChannelMode=NIDAQHardwareAdapterParams::ChannelModeType::TaskPerChannel)
std::map< ChannelHandleType, uint32_t > ChannelIndexMap
const NIDAQHardwareAdapterParams::ChannelModeType ChannelMode
auto GetNumSamples() const noexcept
auto GetSampleSizeInBytes() const noexcept
void AddChannel(ChannelHandleType ChannelHandle, uint64_t NumSamples)
const NIDAQSyms::TaskHandle NITask
std::vector< std::unique_ptr< CircularStream > > ReadStreamPerChannel
std::vector< DigitalValueType > DigitalValues
auto GetBufferSizeInSamples() const noexcept
SamplingModeType
Type to determine how to record/play back to/from the stream.
static constexpr auto HardwareOperationTimeout
Default timeout used to lock the mutex provided by the base class Util::ILockable to synchronize acce...
void ThrowExceptionUnsafe(std::exception_ptr Exception) const
Stores Exception in LastException, wraps it in a Util::ForwardedException and throws the wrapped exce...
auto GetExceptionUnsafe() const
Getter for LastException.
ParamsConstTypeSyncPtrType GetParams(const std::chrono::milliseconds Timeout=GetParamsTimeoutDefault) const
Locks the mutex of the parameter class instance Params assigned to this Object instance and returns a...
Definition Object.cpp:436
Refer to ParamsBase::dispatch_tag.
Definition Object.h:2018
static void DisableUserEditable(ParamBase &Param) noexcept
Sets the UserEditable property of the parameter Param to false. Refer to ParamBase::UserEditable.
Definition Object.cpp:292
Logs events like errors and writes them immediately to a HTML file in a human-readable format....
Definition Util.h:1061
void Log(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &File="", const int ErrorCode=0, const std::stacktrace &Trace={}) noexcept
Logs an event from information specified manually.
Definition Util.cpp:309
LockType AcquireLock(const std::chrono::milliseconds Timeout=DefaultTimeout) const
Locks the internal mutex. Blocks until the mutex is locked or until the timeout duration is exceeded.
Definition Util.cpp:8
An invalid argument like a null pointer has been passed to a function.
Definition Exception.h:137
Data to operate on is invalid for a specific purpose. This indicates a corrupted data structure or fu...
Definition Exception.h:163
Thrown when some operation or feature is temporarily or permanently not available.
Definition Exception.h:286
Thrown when a requested ressource does not exist.
Definition Exception.h:236
Thrown when a numeric operation would result in an overflow (e.g. due to incompatible data types)
Definition Exception.h:199
DynExp's hardware namespace contains the implementation of DynExp hardware adapters which extend DynE...
std::unique_ptr< ParamsBase > ParamsBasePtrType
Alias for a pointer to the parameter system base class ParamsBase.
Definition Object.h:1807
void ForwardException(std::exception_ptr e)
Wraps the exception passed to the function in a ForwardedException and throws the ForwardedException....
Definition Exception.cpp:30
std::string TrimTrailingZeros(const std::string &Str)
Removes trailing zeros ('\0') from a string.
Definition Util.h:833
std::vector< std::pair< TextType, ValueType > > TextValueListType
Type of a list containing key-value pairs where key is a text of type Util::TextType.
Definition QtUtil.h:37
Accumulates include statements to provide a precompiled header.