DynExp
Highly flexible laboratory automation for dynamically changing experiments.
HardwareAdapterNIDAQ.cpp
Go to the documentation of this file.
1 // This file is part of DynExp.
2 
3 #include "stdafx.h"
4 #include "HardwareAdapterNIDAQ.h"
5 
6 namespace DynExpHardware
7 {
9  {
11  { "Buffered transfer", UseOnlyOnBrdMemType::UseMemoryBuffer },
12  { "Direct transfer", UseOnlyOnBrdMemType::OnlyOnboardMemory }
13  };
14 
15  return List;
16  }
17 
19  {
21  }
22 
24  {
26  { "One task per channel", ChannelModeType::TaskPerChannel },
27  { "Combine channels into one task", ChannelModeType::CombineChannels }
28  };
29 
30  return List;
31  }
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 
82  {
83  if (!NITask)
84  throw Util::InvalidArgException("NITask cannot be nullptr.");
85  }
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
108  AnalogValues.resize(GetBufferSizeInSamples(), .0);
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(),
182  UseOnlyOnBrdMem == NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType::OnlyOnboardMemory);
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(),
250  UseOnlyOnBrdMem == NIDAQOutputPortParamsExtension::UseOnlyOnBrdMemType::OnlyOnboardMemory);
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  {
554  auto NIDAQSamplingMode = SamplingMode == DynExpInstr::NumericSampleStreamParamsExtension::SamplingModeType::Single ?
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  {
569  if (TriggerMode != NIDAQHardwareAdapterParams::TriggerModeType::RisingEdge && TriggerMode != NIDAQHardwareAdapterParams::TriggerModeType::FallingEdge)
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))
641  ThrowExceptionUnsafe(std::make_exception_ptr(Util::NotAvailableException(
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 StopTaskUnsafe(NIDAQTask *Task) 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.