DynExp
Highly flexible laboratory automation for dynamically changing experiments.
HardwareAdapterSwabianInstrumentsPulseStreamer.cpp
Go to the documentation of this file.
1 // This file is part of DynExp.
2 
3 #include "stdafx.h"
5 
6 namespace DynExpHardware
7 {
9  {
11  { "DO 0", OutputChannelType::DO0 },
12  { "DO 1", OutputChannelType::DO1 },
13  { "DO 2", OutputChannelType::DO2 },
14  { "DO 3", OutputChannelType::DO3 },
15  { "DO 4", OutputChannelType::DO4 },
16  { "DO 5", OutputChannelType::DO5 },
17  { "DO 6", OutputChannelType::DO6 },
18  { "DO 7", OutputChannelType::DO7 },
19  { "AO 0", OutputChannelType::AO0 },
20  { "AO 1", OutputChannelType::AO1 }
21  };
22 
23  return List;
24  }
25 
27  {
29  { "Immediate", TriggerEdgeType::Immediate },
30  { "Software", TriggerEdgeType::Software },
31  { "Rising Edge", TriggerEdgeType::RisingEdge },
32  { "Falling Edge", TriggerEdgeType::FallingEdge },
33  { "Rising and Falling Edge", TriggerEdgeType::RisingAndFallingEdge }
34  };
35 
36  return List;
37  }
38 
40  {
42  { "Normal", TriggerModeType::Normal },
43  { "Single", TriggerModeType::Single }
44  };
45 
46  return List;
47  }
48 
49  std::unique_ptr<pulse_streamer::PulseMessage> SIPulseStreamerHardwareAdapter::PulseType::ToPulseMessage() const
50  {
51  auto Message = std::make_unique<pulse_streamer::PulseMessage>();
52 
53  Message->set_ticks(ticks);
54  Message->set_digi(digi);
55  Message->set_ao0(ao0);
56  Message->set_ao1(ao1);
57 
58  return Message;
59  }
60 
62  {
63  auto ClippedVoltage = std::min(Voltage, 1.0);
64  ClippedVoltage = std::max(ClippedVoltage, -1.0);
65  ClippedVoltage *= static_cast<double>(0x7ff);
66 
67  bool IsNeg = ClippedVoltage < 0.0;
68  int16_t Value = static_cast<int16_t>(std::floor(std::abs(ClippedVoltage)));
69  Value *= 16; // Shift 4 bits to the left.
70 
71  return Value * (IsNeg ? int16_t(-1) : int16_t(1));
72  }
73 
75  {
76  uint8_t DOValue = 0;
77 
78  if (static_cast<std::underlying_type_t<SIPulseStreamerHardwareAdapterParams::OutputChannelType>>(Channel) <= 7)
79  DOValue |= (static_cast<bool>(Value) << static_cast<std::underlying_type_t<SIPulseStreamerHardwareAdapterParams::OutputChannelType>>(Channel));
80 
81  return DOValue;
82  }
83 
86  {
87  }
88 
90  {
91  try
92  {
93  // Not locking, since the object is being destroyed. This should be inherently thread-safe.
96  }
97  catch (...)
98  {
99  // Swallow any exception in order not to cause abort on error.
100  }
101  }
102 
104  {
106 
108  }
109 
111  {
113 
115  }
116 
118  {
120 
122  }
123 
125  const std::vector<SampleType>& NewSamples) const
126  {
128 
129  SetSamplesUnsafe(OutputChannel, NewSamples);
130  }
131 
132  void SIPulseStreamerHardwareAdapter::SetNumRuns(int64_t NumRuns) const
133  {
135 
137  }
138 
141  {
143 
144  SetTriggerUnsafe(TriggerEdge, TriggerMode);
145  }
146 
148  {
150 
152  }
153 
155  {
157 
159  }
160 
162  {
164 
165  return IsStreamingUnsafe();
166  }
167 
169  {
171 
172  return HasSequenceUnsafe();
173  }
174 
176  {
178 
179  return HasFinishedUnsafe();
180  }
181 
183  {
184  // auto lock = AcquireLock(); not necessary here, since DynExp ensures that Object::Reset() can only
185  // be called if respective object is not in use.
186 
187  Samples.clear();
188  NumRuns = -1; // By default, repeat pulse sequence forever.
189 
190  if (IsOpenedUnsafe())
192 
194  }
195 
196  std::vector<SIPulseStreamerHardwareAdapter::PulseType> SIPulseStreamerHardwareAdapter::ComposePulseSequence() const
197  {
198  std::vector<PulseType> Pulses;
199 
200  for (auto SampleIt = Samples.cbegin(); SampleIt != Samples.cend(); ++SampleIt)
201  {
202  // Duration does not matter for final pulse, thus set to zero in that case.
203  uint32_t Duration = (SampleIt + 1) != Samples.cend() ? Util::NumToT<uint32_t>(((SampleIt + 1)->Timestamp - SampleIt->Timestamp).count()) : 0;
204 
205  if (Pulses.empty())
206  {
207  int16_t AO0Value = SampleIt->Channel == SIPulseStreamerHardwareAdapterParams::OutputChannelType::AO0 ? SampleIt->Value : 0;
208  int16_t AO1Value = SampleIt->Channel == SIPulseStreamerHardwareAdapterParams::OutputChannelType::AO1 ? SampleIt->Value : 0;
209 
210  Pulses.emplace_back(Duration, SampleIt->ComposeDOValue(), AO0Value, AO1Value);
211  }
212  else
213  {
214  int16_t AO0Value = SampleIt->Channel == SIPulseStreamerHardwareAdapterParams::OutputChannelType::AO0 ? SampleIt->Value : Pulses.back().ao0;
215  int16_t AO1Value = SampleIt->Channel == SIPulseStreamerHardwareAdapterParams::OutputChannelType::AO1 ? SampleIt->Value : Pulses.back().ao1;
216 
217  uint8_t DOValue = Pulses.back().digi;
218  if (static_cast<std::underlying_type_t<SIPulseStreamerHardwareAdapterParams::OutputChannelType>>(SampleIt->Channel) <= 7)
219  DOValue = (DOValue & ~(1 << static_cast<std::underlying_type_t<SIPulseStreamerHardwareAdapterParams::OutputChannelType>>(SampleIt->Channel))) | SampleIt->ComposeDOValue();
220 
221  if (Pulses.back().ao0 == AO0Value && Pulses.back().ao1 == AO1Value && Pulses.back().digi == DOValue && (SampleIt + 1) != Samples.cend())
222  Pulses.back().ticks += Duration;
223  else
224  Pulses.emplace_back(Duration, DOValue, AO0Value, AO1Value);
225  }
226  }
227 
228  return Pulses;
229  }
230 
232  {
233  auto DerivedParams = dynamic_Params_cast<SIPulseStreamerHardwareAdapter>(GetParams());
234 
235  SetTriggerUnsafe(DerivedParams->TriggerEdge, DerivedParams->TriggerMode);
236  SetNumRunsUnsafe(DerivedParams->NumRuns);
237  }
238 
240  {
241  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::reset);
242  }
243 
245  {
246  Samples.clear();
247 
248  auto Message = Pulse.ToPulseMessage();
249 
250  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::constant, *Message);
251  }
252 
254  {
255  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::forceFinal);
256  }
257 
259  const std::vector<SampleType>& NewSamples) const
260  {
261  if (NewSamples.empty())
262  ThrowExceptionUnsafe(std::make_exception_ptr(Util::EmptyException("NewSamples must not be empty.")));
263  if (std::find_if(NewSamples.cbegin(), NewSamples.cend(), [OutputChannel](const SampleType& Sample) { return Sample.Channel != OutputChannel; }) !=
264  NewSamples.cend())
265  ThrowExceptionUnsafe(std::make_exception_ptr(Util::InvalidDataException(
266  "The channel of at least one sample in NewSamples does not match the specified OutputChannel.")));
267 
268  std::erase_if(Samples, [OutputChannel](const SampleType& Sample) { return Sample.Channel == OutputChannel; });
269  Samples.insert(Samples.end(), NewSamples.cbegin(), NewSamples.cend());
270  std::sort(Samples.begin(), Samples.end());
271 
272  auto Pulses = ComposePulseSequence();
273  const auto FinalPulse = Pulses.back();
274  Pulses.pop_back();
275 
276  if (!Pulses.empty())
277  {
278  pulse_streamer::SequenceMessage SequenceMessage;
279  for (const auto& Pulse : Pulses)
280  {
281  auto PulseMessage = SequenceMessage.add_pulse();
282  *PulseMessage = *Pulse.ToPulseMessage(); // TODO: Maybe some potential for speed optimization.
283  }
284 
285  SequenceMessage.set_n_runs(NumRuns);
286  SequenceMessage.set_allocated_final(FinalPulse.ToPulseMessage().release());
287 
288  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::stream, SequenceMessage);
289  }
290  else
291  SetConstantOutputUnsafe(FinalPulse);
292  }
293 
295  {
296  this->NumRuns = NumRuns;
297  }
298 
301  {
302  pulse_streamer::TriggerMessage Message;
303 
304  switch (TriggerEdge)
305  {
306  case SIPulseStreamerHardwareAdapterParams::TriggerEdgeType::Software: Message.set_start(pulse_streamer::TriggerMessage_Start::TriggerMessage_Start_SOFTWARE); break;
307  case SIPulseStreamerHardwareAdapterParams::TriggerEdgeType::RisingEdge: Message.set_start(pulse_streamer::TriggerMessage_Start::TriggerMessage_Start_HARDWARE_RISING); break;
308  case SIPulseStreamerHardwareAdapterParams::TriggerEdgeType::FallingEdge: Message.set_start(pulse_streamer::TriggerMessage_Start::TriggerMessage_Start_HARDWARE_FALLING); break;
309  case SIPulseStreamerHardwareAdapterParams::TriggerEdgeType::RisingAndFallingEdge: Message.set_start(pulse_streamer::TriggerMessage_Start::TriggerMessage_Start_HARDWARE_RISING_AND_FALLING); break;
310  default: Message.set_start(pulse_streamer::TriggerMessage_Start::TriggerMessage_Start_IMMEDIATE);
311  }
312 
313  switch (TriggerMode)
314  {
315  case SIPulseStreamerHardwareAdapterParams::TriggerModeType::Normal: Message.set_mode(pulse_streamer::TriggerMessage_Mode::TriggerMessage_Mode_NORMAL); break;
316  default: Message.set_mode(pulse_streamer::TriggerMessage_Mode::TriggerMessage_Mode_SINGLE);
317  }
318 
319  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::setTrigger, Message);
320 
321  }
322 
324  {
325  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::startNow);
326  }
327 
329  {
330  InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::rearm);
331  }
332 
334  {
335  return InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::isStreaming);
336  }
337 
339  {
340  return InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::hasSequence);
341  }
342 
344  {
345  return InvokeStubFunc(&pulse_streamer::PulseStreamer::Stub::hasFinished);
346  }
347 
349  {
350  if (lhs.Timestamp == rhs.Timestamp)
351  return std::strong_ordering::equal;
352  else if (lhs.Timestamp > rhs.Timestamp)
353  return std::strong_ordering::greater;
354  else
355  return std::strong_ordering::less;
356  }
357 }
Implementation of a hardware adapter to control Swabian Instruments Pulse Streamer 8/2 hardware.
static Util::TextValueListType< OutputChannelType > OutputChannelTypeStrList()
static Util::TextValueListType< TriggerEdgeType > TriggerEdgeTypeStrList()
static Util::TextValueListType< TriggerModeType > TriggerModeTypeStrList()
void SetTrigger(SIPulseStreamerHardwareAdapterParams::TriggerEdgeType TriggerEdge, SIPulseStreamerHardwareAdapterParams::TriggerModeType TriggerMode=SIPulseStreamerHardwareAdapterParams::TriggerModeType::Normal) const
virtual void OpenUnsafeChild() override
Override to add additional initialization steps. Gets executed after the gRPC connection has been est...
void SetSamples(SIPulseStreamerHardwareAdapterParams::OutputChannelType OutputChannel, const std::vector< SampleType > &NewSamples) const
std::vector< SampleType > Samples
Combined sample vector of all channels. Assumed to be always sorted.
void SetSamplesUnsafe(SIPulseStreamerHardwareAdapterParams::OutputChannelType OutputChannel, const std::vector< SampleType > &NewSamples) const
SIPulseStreamerHardwareAdapter(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType &&Params)
int64_t NumRuns
How often to repeat the sample sequence. -1 means indefinitely.
void SetTriggerUnsafe(SIPulseStreamerHardwareAdapterParams::TriggerEdgeType TriggerEdge, SIPulseStreamerHardwareAdapterParams::TriggerModeType TriggerMode=SIPulseStreamerHardwareAdapterParams::TriggerModeType::Normal) const
void ResetImpl(dispatch_tag< gRPCHardwareAdapter >) override final
uint32_t InvokeStubFunc(StubFuncPtrType< MessageType > Func, const MessageType &Message) const
This template class provides basic functionality to design hardware adapters for instruments which co...
bool IsOpenedUnsafe() const noexcept
Checks whether the gRPCHardwareAdapter is connected to a server.
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
const std::thread::id OwnerThreadID
Thread id of the thread which has constructed (and owns) this Object instance.
Definition: Object.h:2302
const ParamsBasePtrType Params
Pointer to the parameter class instance belonging to this Object instance.
Definition: Object.h:2303
Refer to ParamsBase::dispatch_tag.
Definition: Object.h:2018
Thrown when a list is expected to contain entries and when a query results in an empty answer or an e...
Definition: Exception.h:224
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
Data to operate on is invalid for a specific purpose. This indicates a corrupted data structure or fu...
Definition: Exception.h:163
DynExp's hardware namespace contains the implementation of DynExp hardware adapters which extend DynE...
std::strong_ordering operator<=>(const SIPulseStreamerHardwareAdapter::SampleType &lhs, const SIPulseStreamerHardwareAdapter::SampleType &rhs)
@ Pulse
Manually defined pulses.
std::unique_ptr< ParamsBase > ParamsBasePtrType
Alias for a pointer to the parameter system base class ParamsBase.
Definition: Object.h:1807
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.
Swabian Instruments Pulse Streamer 8/2's internal representation of a single pulse.
int16_t ao1
Analog out channel 1 (-0x7FFF is -1.0V, 0x7FFF is 1.0V)
uint8_t digi
Digital out bit mask (LSB is channel 0, MSB is channel 7)
int16_t ao0
Analog out channel 0 (-0x7FFF is -1.0V, 0x7FFF is 1.0V)