Line data Source code
1 : /**
2 : * @file FakeHSIEventGeneratorModule.cpp FakeHSIEventGeneratorModule class
3 : * implementation
4 : *
5 : * This is part of the DUNE DAQ Software Suite, copyright 2020.
6 : * Licensing/copyright details are in the COPYING file that you should have
7 : * received with this code.
8 : */
9 :
10 : #include "FakeHSIEventGeneratorModule.hpp"
11 :
12 : #include "appmodel/FakeHSIEventGeneratorModule.hpp"
13 : #include "confmodel/Connection.hpp"
14 : #include "confmodel/DaqModule.hpp"
15 : #include "confmodel/DetectorConfig.hpp"
16 : #include "confmodel/Session.hpp"
17 : #include "dfmessages/HSIEvent.hpp"
18 : #include "iomanager/IOManager.hpp"
19 : #include "logging/Logging.hpp"
20 : #include "rcif/cmd/Nljs.hpp"
21 : #include "utilities/Issues.hpp"
22 :
23 : #include <chrono>
24 : #include <cstdlib>
25 : #include <string>
26 : #include <thread>
27 : #include <vector>
28 :
29 : namespace dunedaq {
30 : namespace hsilibs {
31 :
32 : enum
33 : {
34 : TLVL_ENTER_EXIT_METHODS = 5
35 : };
36 :
37 0 : FakeHSIEventGeneratorModule::FakeHSIEventGeneratorModule(const std::string& name)
38 : : HSIEventSender(name)
39 0 : , m_thread(std::bind(&FakeHSIEventGeneratorModule::do_hsi_work, this, std::placeholders::_1))
40 0 : , m_timestamp_estimator(nullptr)
41 0 : , m_random_generator()
42 0 : , m_uniform_distribution(0, UINT32_MAX)
43 0 : , m_clock_frequency(62500000)
44 0 : , m_trigger_rate(1) // Hz
45 0 : , m_active_trigger_rate(1) // Hz
46 0 : , m_event_period(1e6) // us
47 0 : , m_timestamp_offset(0)
48 0 : , m_hsi_device_id(0)
49 0 : , m_signal_emulation_mode(0)
50 0 : , m_mean_signal_multiplicity(0)
51 0 : , m_enabled_signals(0)
52 0 : , m_generated_counter(0)
53 0 : , m_last_generated_timestamp(0)
54 : {
55 0 : register_command("conf", &FakeHSIEventGeneratorModule::do_configure);
56 0 : register_command("start", &FakeHSIEventGeneratorModule::do_start);
57 0 : register_command("stop_trigger_sources", &FakeHSIEventGeneratorModule::do_stop);
58 0 : register_command("scrap", &FakeHSIEventGeneratorModule::do_scrap);
59 0 : }
60 :
61 : void
62 0 : FakeHSIEventGeneratorModule::init(std::shared_ptr<appfwk::ConfigurationManager> mcfg)
63 : {
64 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering init() method";
65 0 : HSIEventSender::init(mcfg);
66 :
67 0 : m_clock_frequency = mcfg->get_session()->get_detector_configuration()->get_clock_speed_hz();
68 0 : auto mdal =
69 0 : mcfg->get_dal<appmodel::FakeHSIEventGeneratorModule>(get_name()); // Only need generic DaqModule for output
70 :
71 0 : if (!mdal) {
72 0 : throw appfwk::CommandFailed(ERS_HERE, "init", get_name(), "Unable to retrieve configuration object");
73 : }
74 :
75 0 : for (auto con : mdal->get_outputs()) {
76 0 : if (con->get_data_type() == datatype_to_string<HSI_FRAME_STRUCT>()) {
77 :
78 0 : m_raw_hsi_data_sender = get_iom_sender<HSI_FRAME_STRUCT>(con->UID());
79 : }
80 : }
81 :
82 : // 07-Jul-2025, KAB: added the fetching of the TimeSync connection information
83 : // (and the assignment of the TimeSync Receiver) here, now that we have the
84 : // TimeSync connection defined in the configuration. (Previously, the creation
85 : // of the TimeSync receiver was done in the 'start' method, and it used a hard-coded
86 : // wildcard in the connection name lookup.)
87 0 : for (auto con : mdal->get_inputs()) {
88 0 : if (con->get_data_type() == datatype_to_string<dfmessages::TimeSync>()) {
89 0 : m_timesync_receiver = get_iom_receiver<dfmessages::TimeSync>(con->UID());
90 : }
91 : }
92 :
93 0 : m_params = mdal->get_configuration();
94 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting init() method";
95 0 : }
96 :
97 : // void
98 : // FakeHSIEventGeneratorModule::get_info(opmonlib::InfoCollector& ci, int /*level*/)
99 : // {
100 : // // send counters internal to the module
101 : // fakehsieventgeneratorinfo::Info module_info;
102 :
103 : // module_info.generated_hsi_events_counter = m_generated_counter.load();
104 : // module_info.sent_hsi_events_counter = m_sent_counter.load();
105 : // module_info.failed_to_send_hsi_events_counter = m_failed_to_send_counter.load();
106 : // module_info.last_generated_timestamp = m_last_generated_timestamp.load();
107 : // module_info.last_sent_timestamp = m_last_sent_timestamp.load();
108 :
109 : // ci.add(module_info);
110 : // }
111 :
112 : void
113 0 : FakeHSIEventGeneratorModule::do_configure(const CommandData_t& /*obj*/)
114 : {
115 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_configure() method";
116 :
117 0 : if (m_params->get_trigger_rate() > 0) {
118 0 : m_trigger_rate.store(m_params->get_trigger_rate());
119 0 : m_active_trigger_rate.store(m_trigger_rate.load());
120 : } else {
121 0 : ers::fatal(InvalidTriggerRateValue(ERS_HERE, m_params->get_trigger_rate()));
122 : }
123 :
124 : // time between HSI events [us]
125 0 : m_event_period.store(1.e6 / m_active_trigger_rate.load());
126 0 : TLOG() << get_name() << " Setting trigger rate, event period [us] to: " << m_active_trigger_rate.load() << ", "
127 0 : << m_event_period.load();
128 :
129 : // offset in units of clock ticks, positive offset increases timestamp
130 0 : m_timestamp_offset = m_params->get_timestamp_offset();
131 0 : m_hsi_device_id = m_params->get_hsi_device_id();
132 0 : m_signal_emulation_mode = m_params->get_signal_emulation_mode();
133 0 : m_mean_signal_multiplicity = m_params->get_mean_signal_multiplicity();
134 0 : m_enabled_signals = m_params->get_enabled_signals();
135 :
136 : // configure the random distributions
137 0 : m_poisson_distribution = std::poisson_distribution<uint64_t>(m_mean_signal_multiplicity); // NOLINT(build/unsigned)
138 :
139 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_configure() method";
140 0 : }
141 :
142 : void
143 0 : FakeHSIEventGeneratorModule::do_start(const CommandData_t& obj)
144 : {
145 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_start() method";
146 0 : auto start_params = obj.get<rcif::cmd::StartParams>();
147 :
148 0 : m_timestamp_estimator.reset(new utilities::TimestampEstimatorTimeSync(start_params.run, m_clock_frequency));
149 :
150 0 : m_timesync_receiver->add_callback(
151 0 : std::bind(&utilities::TimestampEstimatorTimeSync::timesync_callback<dfmessages::TimeSync>,
152 0 : reinterpret_cast<utilities::TimestampEstimatorTimeSync*>(m_timestamp_estimator.get()),
153 : std::placeholders::_1));
154 :
155 0 : TLOG() << get_name() << " Using trigger rate, event period [us]: " << m_active_trigger_rate.load() << ", "
156 0 : << m_event_period.load();
157 :
158 0 : m_run_number.store(start_params.run);
159 :
160 : // 28-Sep-2023, KAB: added code to wait for the Sender connection to be ready.
161 : // This code needs to come *before* the Sender connection is first used, and
162 : // before any threads that use the Sender connection are start, and it needs
163 : // to come *after* any Receiver connections are created/started so that it
164 : // doesn't block other modules that are trying to connect to this one.
165 : // We retry for 10 seconds. That seems like it should be plenty long enough
166 : // for the connection to be established but short enough so that it doesn't
167 : // annoy users if/when the connection readiness times out.
168 0 : if (!ready_to_send(std::chrono::milliseconds(1))) {
169 0 : bool ready = false;
170 0 : for (int loop_counter = 0; loop_counter < 10; ++loop_counter) {
171 0 : TLOG() << get_name() << " Waiting for the Sender for the " << m_hsievent_send_connection
172 0 : << " connection to be ready to send messages, loop_count=" << (loop_counter + 1);
173 0 : if (ready_to_send(std::chrono::milliseconds(1000))) {
174 : ready = true;
175 : break;
176 : }
177 : }
178 0 : if (ready) {
179 0 : TLOG() << get_name() << " The Sender for the " << m_hsievent_send_connection << " connection is now ready.";
180 : } else {
181 0 : throw(SenderReadyTimeout(ERS_HERE, get_name(), m_hsievent_send_connection));
182 : }
183 : }
184 :
185 0 : m_thread.start_working_thread("fake-tsd-gen");
186 0 : TLOG() << get_name() << " successfully started";
187 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_start() method";
188 0 : }
189 :
190 : void
191 0 : FakeHSIEventGeneratorModule::do_stop(const CommandData_t& /*args*/)
192 : {
193 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_stop() method";
194 0 : m_thread.stop_working_thread();
195 :
196 0 : m_timesync_receiver->remove_callback();
197 0 : TLOG() << get_name() << ": received " << m_timestamp_estimator->get_received_timesync_count()
198 0 : << " TimeSync messages.";
199 :
200 0 : m_timestamp_estimator.reset(nullptr); // Calls TimestampEstimatorTimeSync dtor
201 :
202 0 : m_active_trigger_rate.store(m_trigger_rate.load());
203 0 : m_event_period.store(1.e6 / m_active_trigger_rate.load());
204 0 : TLOG() << get_name() << " Updating trigger rate, event period [us] to: " << m_active_trigger_rate.load() << ", "
205 0 : << m_event_period.load();
206 :
207 0 : TLOG() << get_name() << " successfully stopped";
208 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_stop() method";
209 0 : }
210 :
211 : void
212 0 : FakeHSIEventGeneratorModule::do_scrap(const CommandData_t& /*args*/)
213 : {
214 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_scrap() method";
215 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_scrap() method";
216 0 : }
217 :
218 : uint32_t // NOLINT(build/unsigned)
219 0 : FakeHSIEventGeneratorModule::generate_signal_map()
220 : {
221 :
222 0 : uint32_t signal_map = 0; // NOLINT(build/unsigned)
223 0 : switch (m_signal_emulation_mode) {
224 : case 0:
225 : // 0b11111111 11111111 11111111 11111111
226 : signal_map = UINT32_MAX;
227 : break;
228 : case 1:
229 0 : for (uint i = 0; i < 32; ++i)
230 0 : if (m_poisson_distribution(m_random_generator))
231 0 : signal_map = signal_map | (1UL << i);
232 : break;
233 0 : case 2:
234 0 : signal_map = m_uniform_distribution(m_random_generator);
235 0 : break;
236 0 : default:
237 0 : signal_map = 0;
238 : }
239 0 : TLOG_DEBUG(3) << "raw gen. map: " << std::bitset<32>(signal_map);
240 0 : return signal_map;
241 : }
242 :
243 : void
244 0 : FakeHSIEventGeneratorModule::do_hsi_work(std::atomic<bool>& running_flag)
245 : {
246 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering generate_hsievents() method";
247 :
248 : // Wait for there to be a valid timestsamp estimate before we start
249 : // TODO put in tome sort of timeout? Stoyan Trilov stoyan.trilov@cern.ch
250 0 : if (m_timestamp_estimator.get() != nullptr && m_timestamp_estimator->wait_for_valid_timestamp(running_flag) ==
251 : utilities::TimestampEstimatorBase::kInterrupted) {
252 0 : ers::error(utilities::FailedToGetTimestampEstimate(ERS_HERE));
253 0 : return;
254 : }
255 :
256 0 : m_generated_counter = 0;
257 0 : m_sent_counter = 0;
258 0 : m_last_generated_timestamp = 0;
259 0 : m_last_sent_timestamp = 0;
260 0 : m_failed_to_send_counter = 0;
261 :
262 0 : bool break_flag = false;
263 :
264 0 : auto prev_gen_time = std::chrono::steady_clock::now();
265 :
266 0 : while (!break_flag) {
267 :
268 : // emulate some signals
269 0 : uint32_t signal_map = generate_signal_map(); // NOLINT(build/unsigned)
270 0 : uint32_t trigger_map = signal_map & m_enabled_signals; // NOLINT(build/unsigned)
271 :
272 0 : TLOG_DEBUG(3) << "masked gen. map:" << std::bitset<32>(trigger_map);
273 :
274 : // if at least one active signal, send a HSIEvent
275 0 : if (trigger_map && m_timestamp_estimator.get() != nullptr) {
276 :
277 0 : dfmessages::timestamp_t ts = m_timestamp_estimator->get_timestamp_estimate();
278 :
279 0 : ts += m_timestamp_offset;
280 :
281 0 : ++m_generated_counter;
282 :
283 0 : m_last_generated_timestamp.store(ts);
284 :
285 0 : dfmessages::HSIEvent event =
286 0 : dfmessages::HSIEvent(m_hsi_device_id, trigger_map, ts, m_generated_counter, m_run_number);
287 0 : send_hsi_event(event);
288 :
289 : // Send raw HSI data to a DLH
290 0 : std::array<uint32_t, 7> hsi_struct;
291 0 : hsi_struct[0] = (0x1 << 6) | 0x1; // DAQHeader, frame version: 1, det id: 1
292 0 : hsi_struct[1] = ts;
293 0 : hsi_struct[2] = ts >> 32;
294 0 : hsi_struct[3] = signal_map;
295 0 : hsi_struct[4] = 0x0;
296 0 : hsi_struct[5] = trigger_map;
297 0 : hsi_struct[6] = m_generated_counter;
298 :
299 0 : TLOG_DEBUG(3) << get_name() << ": Formed HSI_FRAME_STRUCT " << std::hex << "0x" << hsi_struct[0] << ", 0x"
300 0 : << hsi_struct[1] << ", 0x" << hsi_struct[2] << ", 0x" << hsi_struct[3] << ", 0x" << hsi_struct[4]
301 0 : << ", 0x" << hsi_struct[5] << ", 0x" << hsi_struct[6] << "\n";
302 :
303 0 : send_raw_hsi_data(hsi_struct, m_raw_hsi_data_sender.get());
304 : }
305 :
306 : // sleep for the configured event period, if trigger ticks are not 0, otherwise do not send anything
307 0 : if (m_active_trigger_rate.load() > 0) {
308 0 : auto next_gen_time = prev_gen_time + std::chrono::microseconds(m_event_period.load());
309 :
310 : // check running_flag periodically
311 0 : auto flag_check_period = std::chrono::milliseconds(1);
312 0 : auto next_flag_check_time = prev_gen_time + flag_check_period;
313 :
314 0 : while (next_gen_time > next_flag_check_time + flag_check_period) {
315 0 : if (!running_flag.load()) {
316 0 : TLOG_DEBUG(0) << "while waiting to generate fake hsi event, negative run gatherer flag detected.";
317 0 : break_flag = true;
318 0 : break;
319 : }
320 0 : std::this_thread::sleep_until(next_flag_check_time);
321 0 : next_flag_check_time = next_flag_check_time + flag_check_period;
322 : }
323 0 : if (break_flag == false) {
324 0 : std::this_thread::sleep_until(next_gen_time);
325 : }
326 :
327 0 : prev_gen_time = next_gen_time;
328 :
329 : } else {
330 0 : std::this_thread::sleep_for(std::chrono::microseconds(250000));
331 0 : continue;
332 : }
333 : }
334 :
335 0 : std::ostringstream oss_summ;
336 0 : oss_summ << ": Exiting the generate_hsievents() method, generated " << m_generated_counter
337 0 : << " HSIEvent messages and successfully sent " << m_sent_counter << " copies. ";
338 0 : ers::info(dunedaq::hsilibs::ProgressUpdate(ERS_HERE, get_name(), oss_summ.str()));
339 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_work() method";
340 0 : }
341 :
342 : } // namespace hsilibs
343 : } // namespace dunedaq
344 :
345 0 : DEFINE_DUNE_DAQ_MODULE(dunedaq::hsilibs::FakeHSIEventGeneratorModule)
346 :
347 : // Local Variables:
348 : // c-basic-offset: 2
349 : // End:
|