artdaq_demo  v3_06_01
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_ ) { std::abort(); }
109  else if ( exit_after_N_seconds_ )
110  {
111  std::exit( 1 );
112  }
113  else if ( exception_after_N_seconds_ )
114  {
115  throw cet::exception( "HardwareInterface" )
116  << "This is an engineered exception designed for testing purposes";
117  }
118  else if ( nADCcounts_after_N_seconds_ >= 0 )
119  {
120  *bytes_read = sizeof( demo::ToyFragment::Header ) + nADCcounts_after_N_seconds_ * sizeof( data_t );
121  }
122  else
123  {
124  // Pretend the hardware hangs
125  while ( true ) {}
126  }
127  }
128 
129  // Make the fake data, starting with the header
130 
131  // Can't handle a fragment whose size isn't evenly divisible by
132  // the demo::ToyFragment::Header::data_t type size in bytes
133  // std::cout << "Bytes to read: " << *bytes_read << ", sizeof(data_t): " <<
134  // sizeof(demo::ToyFragment::Header::data_t) << std::endl;
135  assert( *bytes_read % sizeof( demo::ToyFragment::Header::data_t ) == 0 );
136 
137  demo::ToyFragment::Header* header = reinterpret_cast<demo::ToyFragment::Header*>( buffer );
138 
139  header->event_size = *bytes_read / sizeof( demo::ToyFragment::Header::data_t );
140  header->trigger_number = 99;
141  header->distribution_type = static_cast<uint8_t>( distribution_type_ );
142 
143  // Generate nADCcounts ADC values ranging from 0 to max based on
144  // the desired distribution
145 
146  std::function<data_t()> generator;
147  data_t gen_seed = 0;
148 
149  switch ( distribution_type_ )
150  {
152  generator = [&]() { return static_cast<data_t>( ( *uniform_distn_ )( engine_ ) ); };
153  break;
154 
156  generator = [&]() {
157  do
158  {
159  gen_seed = static_cast<data_t>( std::round( ( *gaussian_distn_ )( engine_ ) ) );
160  } while ( gen_seed > maxADCvalue_ );
161  return gen_seed;
162  };
163  break;
164 
166  {
167  generator = [&]() {
168  if ( ++gen_seed > maxADCvalue_ ) gen_seed = 0;
169  return gen_seed;
170  };
171  }
172  break;
173 
175  case DistributionType::uninit2:
176  break;
177 
178  default:
179  throw cet::exception( "HardwareInterface" ) << "Unknown distribution type specified";
180  }
181 
182  if ( distribution_type_ != DistributionType::uninitialized && distribution_type_ != DistributionType::uninit2 )
183  {
184  std::generate_n( reinterpret_cast<data_t*>( reinterpret_cast<demo::ToyFragment::Header*>( buffer ) + 1 ),
185  nADCcounts_, generator );
186  }
187  }
188  else
189  {
190  throw cet::exception( "ToyHardwareInterface" ) << "Attempt to call FillBuffer when not sending data";
191  }
192 
193  if ( send_calls_ == 0 )
194  {
195  start_time_ = std::chrono::steady_clock::now();
196  TLOG( 50 ) << "FillBuffer has set the start_time_";
197  }
198 
199  if ( usecs_between_sends_ != 0 )
200  {
201  if ( send_calls_ != 0 )
202  {
203 #pragma GCC diagnostic push
204 #pragma GCC diagnostic ignored "-Wsign-compare"
205 
206  auto usecs_since_start = artdaq::TimeUtils::GetElapsedTimeMicroseconds( start_time_ );
207  long delta = (long)( usecs_between_sends_ * send_calls_ ) - usecs_since_start;
208  if ( delta > 0 ) usleep( delta );
209 
210  TLOG( 15 ) << "FillBuffer send_calls=" << send_calls_ << " usecs_since_start=" << usecs_since_start
211  << " delta=" << delta;
212 
213 #pragma GCC diagnostic pop
214  }
215  }
216  ++send_calls_;
217 }
218 
220 {
221  *buffer = reinterpret_cast<char*>(
222  new uint8_t[ sizeof( demo::ToyFragment::Header ) + maxADCcounts_ * sizeof( data_t ) ] );
223 }
224 
225 void ToyHardwareInterface::FreeReadoutBuffer( char* buffer ) { delete[] buffer; }
226 
228 {
229  // Pretend that the "BoardType" is some vendor-defined integer which
230  // differs from the fragment_type_ we want to use as developers (and
231  // which must be between 1 and 224, inclusive) so add an offset
232  return static_cast<int>( fragment_type_ ) + 1000;
233 }
234 
236 {
237  switch ( fragment_type_ )
238  {
239  case demo::FragmentType::TOY1:
240  return 12;
241  break;
242  case demo::FragmentType::TOY2:
243  return 14;
244  break;
245  default:
246  throw cet::exception( "ToyHardwareInterface" ) << "Unknown board type " << fragment_type_ << " ("
247  << demo::fragmentTypeToString( fragment_type_ ) << ").\n";
248  };
249 }
250 
252 {
253  // Serial number is generated from the uniform distribution on initialization of the class
254  return serial_number_;
255 }
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.