artdaq_demo  v3_08_00
ToyHardwareInterface.cc
1 #include "artdaq-demo/Generators/ToyHardwareInterface/ToyHardwareInterface.hh"
2 #define TRACE_NAME "ToyHardwareInterface"
3 #include "artdaq-core-demo/Overlays/FragmentType.hh"
4 #include "artdaq-core-demo/Overlays/ToyFragment.hh"
5 #include "artdaq/DAQdata/Globals.hh"
6 
7 #include "cetlib_except/exception.h"
8 #include "fhiclcpp/ParameterSet.h"
9 
10 #include <unistd.h>
11 #include <cstdlib>
12 #include <iostream>
13 #include <random>
14 
15 // JCF, Mar-17-2016
16 
17 // ToyHardwareInterface is meant to mimic a vendor-provided hardware
18 // API, usable within the the ToySimulator fragment generator. For
19 // purposes of realism, it's a C++03-style API, as opposed to, say, one
20 // based in C++11 capable of taking advantage of smart pointers, etc.
21 
22 ToyHardwareInterface::ToyHardwareInterface(fhicl::ParameterSet const& ps)
23  : taking_data_(false)
24  , nADCcounts_(ps.get<size_t>("nADCcounts", 40))
25  , maxADCcounts_(ps.get<size_t>("maxADCcounts", 50000000))
26  , change_after_N_seconds_(ps.get<size_t>("change_after_N_seconds", std::numeric_limits<size_t>::max()))
27  , nADCcounts_after_N_seconds_(ps.get<int>("nADCcounts_after_N_seconds", nADCcounts_))
28  , exception_after_N_seconds_(ps.get<bool>("exception_after_N_seconds", false))
29  , exit_after_N_seconds_(ps.get<bool>("exit_after_N_seconds", false))
30  , abort_after_N_seconds_(ps.get<bool>("abort_after_N_seconds", false))
31  , fragment_type_(demo::toFragmentType(ps.get<std::string>("fragment_type")))
32  , maxADCvalue_(pow(2, NumADCBits()) - 1)
33  , // MUST be after "fragment_type"
34  throttle_usecs_(ps.get<size_t>("throttle_usecs", 100000))
35  , usecs_between_sends_(ps.get<size_t>("usecs_between_sends", 0))
36  , distribution_type_(static_cast<DistributionType>(ps.get<int>("distribution_type")))
37  , engine_(ps.get<int64_t>("random_seed", 314159))
38  , uniform_distn_(new std::uniform_int_distribution<data_t>(0, maxADCvalue_))
39  , gaussian_distn_(new std::normal_distribution<double>(0.5 * maxADCvalue_, 0.1 * maxADCvalue_))
40  , start_time_(fake_time_)
41  , send_calls_(0)
42  , serial_number_((*uniform_distn_)(engine_))
43 {
44 #pragma GCC diagnostic push
45 #pragma GCC diagnostic ignored "-Wsign-compare"
46 
47  // JCF, Aug-14-2016
48 
49  // The logic of checking that nADCcounts_after_N_seconds_ >= 0,
50  // below, is because signed vs. unsigned comparison won't do what
51  // you want it to do if nADCcounts_after_N_seconds_ is negative
52 
53  if (nADCcounts_ > maxADCcounts_ ||
54  (nADCcounts_after_N_seconds_ >= 0 && nADCcounts_after_N_seconds_ > maxADCcounts_))
55  {
56  throw cet::exception("HardwareInterface")
57  << "Either (or both) of \"nADCcounts\" and \"nADCcounts_after_N_seconds\""
58  << " is larger than the \"maxADCcounts\" setting (currently at " << maxADCcounts_ << ")";
59  }
60 
61  bool planned_disruption = nADCcounts_after_N_seconds_ != nADCcounts_ || exception_after_N_seconds_ ||
62  exit_after_N_seconds_ || abort_after_N_seconds_;
63 
64  if (planned_disruption && change_after_N_seconds_ == std::numeric_limits<size_t>::max())
65  {
66  throw cet::exception("HardwareInterface") << "A FHiCL parameter designed to create a disruption has been "
67  "set, so \"change_after_N_seconds\" should be set as well";
68 #pragma GCC diagnostic pop
69  }
70 }
71 
72 // JCF, Mar-18-2017
73 
74 // "StartDatataking" is meant to mimic actions one would take when
75 // telling the hardware to start sending data - the uploading of
76 // values to registers, etc.
77 
79 {
80  taking_data_ = true;
81  send_calls_ = 0;
82 }
83 
85 {
86  taking_data_ = false;
87  start_time_ = fake_time_;
88 }
89 
90 void ToyHardwareInterface::FillBuffer(char* buffer, size_t* bytes_read)
91 {
92  if (taking_data_)
93  {
94  usleep(throttle_usecs_);
95 
96  auto elapsed_secs_since_datataking_start = artdaq::TimeUtils::GetElapsedTime(start_time_);
97 
98 #pragma GCC diagnostic push
99 #pragma GCC diagnostic ignored "-Wsign-compare"
100  if (elapsed_secs_since_datataking_start < change_after_N_seconds_ || send_calls_ == 0)
101  {
102 #pragma GCC diagnostic pop
103 
104  *bytes_read = sizeof(demo::ToyFragment::Header) + nADCcounts_ * sizeof(data_t);
105  }
106  else
107  {
108  if (abort_after_N_seconds_)
109  {
110  std::abort();
111  }
112  else if (exit_after_N_seconds_)
113  {
114  std::exit(1);
115  }
116  else if (exception_after_N_seconds_)
117  {
118  throw cet::exception("HardwareInterface")
119  << "This is an engineered exception designed for testing purposes";
120  }
121  else if (nADCcounts_after_N_seconds_ >= 0)
122  {
123  *bytes_read = sizeof(demo::ToyFragment::Header) + nADCcounts_after_N_seconds_ * sizeof(data_t);
124  }
125  else
126  {
127  // Pretend the hardware hangs
128  while (true)
129  {
130  }
131  }
132  }
133 
134  // Make the fake data, starting with the header
135 
136  // Can't handle a fragment whose size isn't evenly divisible by
137  // the demo::ToyFragment::Header::data_t type size in bytes
138  // std::cout << "Bytes to read: " << *bytes_read << ", sizeof(data_t): " <<
139  // sizeof(demo::ToyFragment::Header::data_t) << std::endl;
140  assert(*bytes_read % sizeof(demo::ToyFragment::Header::data_t) == 0);
141 
142  demo::ToyFragment::Header* header = reinterpret_cast<demo::ToyFragment::Header*>(buffer);
143 
144  header->event_size = *bytes_read / sizeof(demo::ToyFragment::Header::data_t);
145  header->trigger_number = 99;
146  header->distribution_type = static_cast<uint8_t>(distribution_type_);
147 
148  // Generate nADCcounts ADC values ranging from 0 to max based on
149  // the desired distribution
150 
151  std::function<data_t()> generator;
152  data_t gen_seed = 0;
153 
154  switch (distribution_type_)
155  {
157  generator = [&]() { return static_cast<data_t>((*uniform_distn_)(engine_)); };
158  break;
159 
161  generator = [&]() {
162  do
163  {
164  gen_seed = static_cast<data_t>(std::round((*gaussian_distn_)(engine_)));
165  } while (gen_seed > maxADCvalue_);
166  return gen_seed;
167  };
168  break;
169 
171  {
172  generator = [&]() {
173  if (++gen_seed > maxADCvalue_) gen_seed = 0;
174  return gen_seed;
175  };
176  }
177  break;
178 
180  case DistributionType::uninit2:
181  break;
182 
183  default:
184  throw cet::exception("HardwareInterface") << "Unknown distribution type specified";
185  }
186 
187  if (distribution_type_ != DistributionType::uninitialized && distribution_type_ != DistributionType::uninit2)
188  {
189  std::generate_n(reinterpret_cast<data_t*>(reinterpret_cast<demo::ToyFragment::Header*>(buffer) + 1),
190  nADCcounts_, generator);
191  }
192  }
193  else
194  {
195  throw cet::exception("ToyHardwareInterface") << "Attempt to call FillBuffer when not sending data";
196  }
197 
198  if (send_calls_ == 0)
199  {
200  start_time_ = std::chrono::steady_clock::now();
201  TLOG(50) << "FillBuffer has set the start_time_";
202  }
203 
204  if (usecs_between_sends_ != 0)
205  {
206  if (send_calls_ != 0)
207  {
208 #pragma GCC diagnostic push
209 #pragma GCC diagnostic ignored "-Wsign-compare"
210 
211  auto usecs_since_start = artdaq::TimeUtils::GetElapsedTimeMicroseconds(start_time_);
212  long delta = (long)(usecs_between_sends_ * send_calls_) - usecs_since_start;
213  if (delta > 0) usleep(delta);
214 
215  TLOG(15) << "FillBuffer send_calls=" << send_calls_ << " usecs_since_start=" << usecs_since_start
216  << " delta=" << delta;
217 
218 #pragma GCC diagnostic pop
219  }
220  }
221  ++send_calls_;
222 }
223 
225 {
226  *buffer = reinterpret_cast<char*>(
227  new uint8_t[sizeof(demo::ToyFragment::Header) + maxADCcounts_ * sizeof(data_t)]);
228 }
229 
230 void ToyHardwareInterface::FreeReadoutBuffer(char* buffer) { delete[] buffer; }
231 
233 {
234  // Pretend that the "BoardType" is some vendor-defined integer which
235  // differs from the fragment_type_ we want to use as developers (and
236  // which must be between 1 and 224, inclusive) so add an offset
237  return static_cast<int>(fragment_type_) + 1000;
238 }
239 
241 {
242  switch (fragment_type_)
243  {
244  case demo::FragmentType::TOY1:
245  return 12;
246  break;
247  case demo::FragmentType::TOY2:
248  return 14;
249  break;
250  default:
251  throw cet::exception("ToyHardwareInterface") << "Unknown board type " << fragment_type_ << " ("
252  << demo::fragmentTypeToString(fragment_type_) << ").\n";
253  };
254 }
255 
257 {
258  // Serial number is generated from the uniform distribution on initialization of the class
259  return serial_number_;
260 }
int NumADCBits() const
Get the number of ADC bits used in generating data.
void StartDatataking()
&quot;StartDatataking&quot; is meant to mimic actions one would take when telling the hardware to start sending...
uint16_t data_t
The type used to represent ADC counts (which are 12 or 14 bits, for TOY1 or TOY2) ...
void StopDatataking()
Performs shutdown actions.
void FillBuffer(char *buffer, size_t *bytes_read)
Use configured generator to fill a buffer with data.
DistributionType
Allow for the selection of output distribution.
void FreeReadoutBuffer(char *buffer)
Release the given buffer to the hardware.
A monotonically-increasing distribution.
ToyHardwareInterface(fhicl::ParameterSet const &ps)
Construct and configure ToyHardwareInterface.
A use-after-free expliot distribution.
void AllocateReadoutBuffer(char **buffer)
Request a buffer from the hardware.
int SerialNumber() const
Gets the serial number of the simulated hardware.
int BoardType() const
Return the &quot;board type&quot; of the simulated hardware.