artdaq_demo  3.12.07
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  , pause_after_N_seconds_(ps.get<size_t>("pause_after_N_seconds", 0))
28  , nADCcounts_after_N_seconds_(ps.get<size_t>("nADCcounts_after_N_seconds", nADCcounts_))
29  , exception_after_N_seconds_(ps.get<bool>("exception_after_N_seconds", false))
30  , exit_after_N_seconds_(ps.get<bool>("exit_after_N_seconds", false))
31  , abort_after_N_seconds_(ps.get<bool>("abort_after_N_seconds", false))
32  , hang_after_N_seconds_(ps.get<bool>("hang_after_N_seconds", false))
33  , fragment_type_(demo::toFragmentType(ps.get<std::string>("fragment_type")))
34  , maxADCvalue_(static_cast<size_t>(pow(2, NumADCBits()) - 1))
35  , // MUST be after "fragment_type"
36  throttle_usecs_(ps.get<size_t>("throttle_usecs", 100000))
37  , usecs_between_sends_(ps.get<size_t>("usecs_between_sends", 0))
38  , distribution_type_(static_cast<DistributionType>(ps.get<int>("distribution_type")))
39  , engine_(ps.get<int64_t>("random_seed", 314159))
40  , uniform_distn_(new std::uniform_int_distribution<data_t>(0, maxADCvalue_))
41  , gaussian_distn_(new std::normal_distribution<double>(0.5 * maxADCvalue_, 0.1 * maxADCvalue_))
42  , start_time_(fake_time_)
43  , send_calls_(0)
44  , serial_number_((*uniform_distn_)(engine_))
45 {
46  if (nADCcounts_ > maxADCcounts_ ||
47  nADCcounts_after_N_seconds_ > maxADCcounts_)
48  {
49  throw cet::exception("HardwareInterface") // NOLINT(cert-err60-cpp)
50  << R"(Either (or both) of "nADCcounts" and "nADCcounts_after_N_seconds")"
51  << " is larger than the \"maxADCcounts\" setting (currently at " << maxADCcounts_ << ")";
52  }
53 
54  bool planned_disruption = nADCcounts_after_N_seconds_ != nADCcounts_ || exception_after_N_seconds_ ||
55  exit_after_N_seconds_ || abort_after_N_seconds_;
56 
57  if (planned_disruption && change_after_N_seconds_ == std::numeric_limits<size_t>::max())
58  {
59  throw cet::exception("HardwareInterface") << "A FHiCL parameter designed to create a disruption has been " // NOLINT(cert-err60-cpp)
60  "set, so \"change_after_N_seconds\" should be set as well";
61  }
62 }
63 
64 // JCF, Mar-18-2017
65 
66 // "StartDatataking" is meant to mimic actions one would take when
67 // telling the hardware to start sending data - the uploading of
68 // values to registers, etc.
69 
71 {
72  taking_data_ = true;
73  send_calls_ = 0;
74 }
75 
77 {
78  taking_data_ = false;
79  start_time_ = fake_time_;
80 }
81 
82 void ToyHardwareInterface::FillBuffer(char* buffer, size_t* bytes_read)
83 {
84  TLOG(TLVL_TRACE) << "FillBuffer BEGIN";
85  if (taking_data_)
86  {
87  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Sleeping for " << throttle_usecs_ << " microseconds";
88  usleep(throttle_usecs_);
89 
90  auto elapsed_secs_since_datataking_start = artdaq::TimeUtils::GetElapsedTime(start_time_);
91  if (elapsed_secs_since_datataking_start < 0) elapsed_secs_since_datataking_start = 0;
92 
93  if (static_cast<size_t>(elapsed_secs_since_datataking_start) < change_after_N_seconds_ || send_calls_ == 0)
94  {
95  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Setting bytes_read to " << sizeof(demo::ToyFragment::Header) + nADCcounts_ * sizeof(data_t);
96  *bytes_read = sizeof(demo::ToyFragment::Header) + nADCcounts_ * sizeof(data_t);
97  }
98  else
99  {
100  if (abort_after_N_seconds_)
101  {
102  TLOG(TLVL_ERROR) << "Engineered Abort!";
103  std::abort();
104  }
105  else if (exit_after_N_seconds_)
106  {
107  TLOG(TLVL_ERROR) << "Engineered Exit!";
108  std::exit(1);
109  }
110  else if (exception_after_N_seconds_)
111  {
112  TLOG(TLVL_ERROR) << "Engineered Exception!";
113  throw cet::exception("HardwareInterface") // NOLINT(cert-err60-cpp)
114  << "This is an engineered exception designed for testing purposes";
115  }
116  else if (hang_after_N_seconds_)
117  {
118  TLOG(TLVL_ERROR) << "Pretending that the hardware has hung! Variable name for gdb: hardwareIsHung";
119  volatile bool hardwareIsHung = true;
120  // Pretend the hardware hangs
121  while (hardwareIsHung)
122  {
123  usleep(10000);
124  }
125  }
126  else
127  {
128  if ((pause_after_N_seconds_ != 0u) && (static_cast<size_t>(elapsed_secs_since_datataking_start) % change_after_N_seconds_ == 0))
129  {
130  TLOG(TLVL_DEBUG + 3) << "pausing " << pause_after_N_seconds_ << " seconds";
131  sleep(pause_after_N_seconds_);
132  TLOG(TLVL_DEBUG + 3) << "resuming after pause of " << pause_after_N_seconds_ << " seconds";
133  }
134  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Setting bytes_read to " << sizeof(demo::ToyFragment::Header) + nADCcounts_after_N_seconds_ * sizeof(data_t);
135  *bytes_read = sizeof(demo::ToyFragment::Header) + nADCcounts_after_N_seconds_ * sizeof(data_t);
136  }
137  }
138 
139  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Making the fake data, starting with the header";
140 
141  // Can't handle a fragment whose size isn't evenly divisible by
142  // the demo::ToyFragment::Header::data_t type size in bytes
143  // std::cout << "Bytes to read: " << *bytes_read << ", sizeof(data_t): " <<
144  // sizeof(demo::ToyFragment::Header::data_t) << std::endl;
145  assert(*bytes_read % sizeof(demo::ToyFragment::Header::data_t) == 0);
146 
147  auto* header = reinterpret_cast<demo::ToyFragment::Header*>(buffer); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
148 
149  header->event_size = *bytes_read / sizeof(demo::ToyFragment::Header::data_t);
150  header->trigger_number = 99;
151  header->distribution_type = static_cast<uint8_t>(distribution_type_);
152 
153  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Generating nADCcounts ADC values ranging from 0 to max based on the desired distribution";
154 
155  std::function<data_t()> generator;
156  data_t gen_seed = 0;
157 
158  switch (distribution_type_)
159  {
161  generator = [&]() { return static_cast<data_t>((*uniform_distn_)(engine_)); };
162  break;
163 
165  generator = [&]() {
166  do
167  {
168  gen_seed = static_cast<data_t>(std::round((*gaussian_distn_)(engine_)));
169  } while (gen_seed > maxADCvalue_);
170  return gen_seed;
171  };
172  break;
173 
175  generator = [&]() {
176  if (++gen_seed > maxADCvalue_)
177  {
178  gen_seed = 0;
179  }
180  return gen_seed;
181  };
182  }
183  break;
184 
186  case DistributionType::uninit2:
187  break;
188 
189  default:
190  throw cet::exception("HardwareInterface") << "Unknown distribution type specified"; // NOLINT(cert-err60-cpp)
191  }
192 
193  if (distribution_type_ != DistributionType::uninitialized && distribution_type_ != DistributionType::uninit2)
194  {
195  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Calling generate_n";
196  std::generate_n(reinterpret_cast<data_t*>(reinterpret_cast<demo::ToyFragment::Header*>(buffer) + 1), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
197  nADCcounts_, generator);
198  }
199  }
200  else
201  {
202  throw cet::exception("ToyHardwareInterface") << "Attempt to call FillBuffer when not sending data"; // NOLINT(cert-err60-cpp)
203  }
204 
205  if (send_calls_ == 0)
206  {
207  TLOG(TLVL_DEBUG + 3) << "FillBuffer has set the start_time_";
208  start_time_ = std::chrono::steady_clock::now();
209  }
210 
211  if (usecs_between_sends_ != 0)
212  {
213  if (send_calls_ > 0)
214  {
215  auto usecs_since_start = artdaq::TimeUtils::GetElapsedTimeMicroseconds(start_time_);
216  double delta = static_cast<double>(usecs_between_sends_ * send_calls_) - usecs_since_start;
217  TLOG(TLVL_DEBUG + 3) << "FillBuffer send_calls=" << send_calls_ << " usecs_since_start=" << usecs_since_start
218  << " delta=" << delta;
219  if (delta > 0)
220  {
221  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Sleeping for " << delta << " microseconds";
222  usleep(delta);
223  }
224  }
225  }
226  ++send_calls_;
227  TLOG(TLVL_TRACE) << "FillBuffer END";
228 }
229 
231 {
232  *buffer = reinterpret_cast<char*>( // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
233  new uint8_t[sizeof(demo::ToyFragment::Header) + maxADCcounts_ * sizeof(data_t)]);
234 }
235 
236 void ToyHardwareInterface::FreeReadoutBuffer(const char* buffer) { delete[] buffer; }
237 
239 {
240  // Pretend that the "BoardType" is some vendor-defined integer which
241  // differs from the fragment_type_ we want to use as developers (and
242  // which must be between 1 and 224, inclusive) so add an offset
243  return static_cast<int>(fragment_type_) + 1000;
244 }
245 
247 {
248  switch (fragment_type_)
249  {
250  case demo::FragmentType::TOY1:
251  return 12;
252  break;
253  case demo::FragmentType::TOY2:
254  return 14;
255  break;
256  default:
257  throw cet::exception("ToyHardwareInterface") << "Unknown board type " << fragment_type_ << " (" // NOLINT(cert-err60-cpp)
258  << demo::fragmentTypeToString(fragment_type_) << ").\n";
259  };
260 }
261 
263 {
264  // Serial number is generated from the uniform distribution on initialization of the class
265  return serial_number_;
266 }
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(const 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.