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