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 0 : FakeHSIEventGeneratorModule::do_configure(const CommandData_t& /*obj*/)
99 : {
100 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_configure() method";
101 :
102 0 : if (m_params->get_trigger_rate() > 0) {
103 0 : m_trigger_rate.store(m_params->get_trigger_rate());
104 0 : m_active_trigger_rate.store(m_trigger_rate.load());
105 : } else {
106 0 : ers::fatal(InvalidTriggerRateValue(ERS_HERE, m_params->get_trigger_rate()));
107 : }
108 :
109 : // time between HSI events [us]
110 0 : m_event_period.store(1.e6 / m_active_trigger_rate.load());
111 0 : TLOG() << get_name() << " Setting trigger rate, event period [us] to: " << m_active_trigger_rate.load() << ", "
112 0 : << m_event_period.load();
113 :
114 : // offset in units of clock ticks, positive offset increases timestamp
115 0 : m_timestamp_offset = m_params->get_timestamp_offset();
116 0 : m_hsi_device_id = m_params->get_hsi_device_id();
117 0 : m_signal_emulation_mode = m_params->get_signal_emulation_mode();
118 0 : m_mean_signal_multiplicity = m_params->get_mean_signal_multiplicity();
119 0 : m_enabled_signals = m_params->get_enabled_signals();
120 :
121 : // configure the random distributions
122 0 : m_poisson_distribution = std::poisson_distribution<uint64_t>(m_mean_signal_multiplicity); // NOLINT(build/unsigned)
123 :
124 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_configure() method";
125 0 : }
126 :
127 : void
128 0 : FakeHSIEventGeneratorModule::do_start(const CommandData_t& obj)
129 : {
130 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_start() method";
131 0 : auto start_params = obj.get<rcif::cmd::StartParams>();
132 :
133 0 : m_timestamp_estimator.reset(new utilities::TimestampEstimatorTimeSync(start_params.run, m_clock_frequency));
134 :
135 0 : m_timesync_receiver->add_callback(
136 0 : std::bind(&utilities::TimestampEstimatorTimeSync::timesync_callback<dfmessages::TimeSync>,
137 0 : reinterpret_cast<utilities::TimestampEstimatorTimeSync*>(m_timestamp_estimator.get()),
138 : std::placeholders::_1));
139 :
140 0 : TLOG() << get_name() << " Using trigger rate, event period [us]: " << m_active_trigger_rate.load() << ", "
141 0 : << m_event_period.load();
142 :
143 0 : m_run_number.store(start_params.run);
144 :
145 : // 28-Sep-2023, KAB: added code to wait for the Sender connection to be ready.
146 : // This code needs to come *before* the Sender connection is first used, and
147 : // before any threads that use the Sender connection are start, and it needs
148 : // to come *after* any Receiver connections are created/started so that it
149 : // doesn't block other modules that are trying to connect to this one.
150 : // We retry for 10 seconds. That seems like it should be plenty long enough
151 : // for the connection to be established but short enough so that it doesn't
152 : // annoy users if/when the connection readiness times out.
153 0 : if (!ready_to_send(std::chrono::milliseconds(1))) {
154 0 : bool ready = false;
155 0 : for (int loop_counter = 0; loop_counter < 10; ++loop_counter) {
156 0 : TLOG() << get_name() << " Waiting for the Sender for the " << m_hsievent_send_connection
157 0 : << " connection to be ready to send messages, loop_count=" << (loop_counter + 1);
158 0 : if (ready_to_send(std::chrono::milliseconds(1000))) {
159 : ready = true;
160 : break;
161 : }
162 : }
163 0 : if (ready) {
164 0 : TLOG() << get_name() << " The Sender for the " << m_hsievent_send_connection << " connection is now ready.";
165 : } else {
166 0 : throw(SenderReadyTimeout(ERS_HERE, get_name(), m_hsievent_send_connection));
167 : }
168 : }
169 :
170 0 : m_thread.start_working_thread("fake-tsd-gen");
171 0 : TLOG() << get_name() << " successfully started";
172 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_start() method";
173 0 : }
174 :
175 : void
176 0 : FakeHSIEventGeneratorModule::do_stop(const CommandData_t& /*args*/)
177 : {
178 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_stop() method";
179 0 : m_thread.stop_working_thread();
180 :
181 0 : m_timesync_receiver->remove_callback();
182 0 : TLOG() << get_name() << ": received " << m_timestamp_estimator->get_received_timesync_count()
183 0 : << " TimeSync messages.";
184 :
185 0 : m_timestamp_estimator.reset(nullptr); // Calls TimestampEstimatorTimeSync dtor
186 :
187 0 : m_active_trigger_rate.store(m_trigger_rate.load());
188 0 : m_event_period.store(1.e6 / m_active_trigger_rate.load());
189 0 : TLOG() << get_name() << " Updating trigger rate, event period [us] to: " << m_active_trigger_rate.load() << ", "
190 0 : << m_event_period.load();
191 :
192 0 : TLOG() << get_name() << " successfully stopped";
193 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_stop() method";
194 0 : }
195 :
196 : void
197 0 : FakeHSIEventGeneratorModule::do_scrap(const CommandData_t& /*args*/)
198 : {
199 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_scrap() method";
200 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_scrap() method";
201 0 : }
202 :
203 : uint32_t // NOLINT(build/unsigned)
204 0 : FakeHSIEventGeneratorModule::generate_signal_map()
205 : {
206 :
207 0 : uint32_t signal_map = 0; // NOLINT(build/unsigned)
208 0 : switch (m_signal_emulation_mode) {
209 : case 0:
210 : // 0b11111111 11111111 11111111 11111111
211 : signal_map = UINT32_MAX;
212 : break;
213 : case 1:
214 0 : for (uint i = 0; i < 32; ++i)
215 0 : if (m_poisson_distribution(m_random_generator))
216 0 : signal_map = signal_map | (1UL << i);
217 : break;
218 0 : case 2:
219 0 : signal_map = m_uniform_distribution(m_random_generator);
220 0 : break;
221 0 : default:
222 0 : signal_map = 0;
223 : }
224 0 : TLOG_DEBUG(3) << "raw gen. map: " << std::bitset<32>(signal_map);
225 0 : return signal_map;
226 : }
227 :
228 : void
229 0 : FakeHSIEventGeneratorModule::do_hsi_work(std::atomic<bool>& running_flag)
230 : {
231 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering generate_hsievents() method";
232 :
233 : // Wait for there to be a valid timestsamp estimate before we start
234 : // TODO put in tome sort of timeout? Stoyan Trilov stoyan.trilov@cern.ch
235 0 : if (m_timestamp_estimator.get() != nullptr && m_timestamp_estimator->wait_for_valid_timestamp(running_flag) ==
236 : utilities::TimestampEstimatorBase::kInterrupted) {
237 0 : ers::error(utilities::FailedToGetTimestampEstimate(ERS_HERE));
238 0 : return;
239 : }
240 :
241 0 : m_generated_counter = 0;
242 0 : m_sent_counter = 0;
243 0 : m_last_generated_timestamp = 0;
244 0 : m_last_sent_timestamp = 0;
245 0 : m_failed_to_send_counter = 0;
246 :
247 0 : bool break_flag = false;
248 :
249 0 : auto prev_gen_time = std::chrono::steady_clock::now();
250 :
251 0 : while (!break_flag) {
252 :
253 : // emulate some signals
254 0 : uint32_t signal_map = generate_signal_map(); // NOLINT(build/unsigned)
255 0 : uint32_t trigger_map = signal_map & m_enabled_signals; // NOLINT(build/unsigned)
256 :
257 0 : TLOG_DEBUG(3) << "masked gen. map:" << std::bitset<32>(trigger_map);
258 :
259 : // if at least one active signal, send a HSIEvent
260 0 : if (trigger_map && m_timestamp_estimator.get() != nullptr) {
261 :
262 0 : dfmessages::timestamp_t ts = m_timestamp_estimator->get_timestamp_estimate();
263 :
264 0 : ts += m_timestamp_offset;
265 :
266 0 : ++m_generated_counter;
267 :
268 0 : m_last_generated_timestamp.store(ts);
269 :
270 0 : dfmessages::HSIEvent event =
271 0 : dfmessages::HSIEvent(m_hsi_device_id, trigger_map, ts, m_generated_counter, m_run_number);
272 0 : send_hsi_event(event);
273 :
274 : // Send raw HSI data to a DLH
275 0 : std::array<uint32_t, 7> hsi_struct;
276 0 : hsi_struct[0] = (0x1 << 6) | 0x1; // DAQHeader, frame version: 1, det id: 1
277 0 : hsi_struct[1] = ts;
278 0 : hsi_struct[2] = ts >> 32;
279 0 : hsi_struct[3] = signal_map;
280 0 : hsi_struct[4] = 0x0;
281 0 : hsi_struct[5] = trigger_map;
282 0 : hsi_struct[6] = m_generated_counter;
283 :
284 0 : TLOG_DEBUG(3) << get_name() << ": Formed HSI_FRAME_STRUCT " << std::hex << "0x" << hsi_struct[0] << ", 0x"
285 0 : << hsi_struct[1] << ", 0x" << hsi_struct[2] << ", 0x" << hsi_struct[3] << ", 0x" << hsi_struct[4]
286 0 : << ", 0x" << hsi_struct[5] << ", 0x" << hsi_struct[6] << "\n";
287 :
288 0 : send_raw_hsi_data(hsi_struct, m_raw_hsi_data_sender.get());
289 : }
290 :
291 : // sleep for the configured event period, if trigger ticks are not 0, otherwise do not send anything
292 0 : if (m_active_trigger_rate.load() > 0) {
293 0 : auto next_gen_time = prev_gen_time + std::chrono::microseconds(m_event_period.load());
294 :
295 : // check running_flag periodically
296 0 : auto flag_check_period = std::chrono::milliseconds(1);
297 0 : auto next_flag_check_time = prev_gen_time + flag_check_period;
298 :
299 0 : while (next_gen_time > next_flag_check_time + flag_check_period) {
300 0 : if (!running_flag.load()) {
301 0 : TLOG_DEBUG(0) << "while waiting to generate fake hsi event, negative run gatherer flag detected.";
302 0 : break_flag = true;
303 0 : break;
304 : }
305 0 : std::this_thread::sleep_until(next_flag_check_time);
306 0 : next_flag_check_time = next_flag_check_time + flag_check_period;
307 : }
308 0 : if (break_flag == false) {
309 0 : std::this_thread::sleep_until(next_gen_time);
310 : }
311 :
312 0 : prev_gen_time = next_gen_time;
313 :
314 : } else {
315 0 : std::this_thread::sleep_for(std::chrono::microseconds(250000));
316 0 : continue;
317 : }
318 : }
319 :
320 0 : std::ostringstream oss_summ;
321 0 : oss_summ << ": Exiting the generate_hsievents() method, generated " << m_generated_counter
322 0 : << " HSIEvent messages and successfully sent " << m_sent_counter << " copies. ";
323 0 : ers::info(dunedaq::hsilibs::ProgressUpdate(ERS_HERE, get_name(), oss_summ.str()));
324 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_work() method";
325 0 : }
326 :
327 : } // namespace hsilibs
328 : } // namespace dunedaq
329 :
330 0 : DEFINE_DUNE_DAQ_MODULE(dunedaq::hsilibs::FakeHSIEventGeneratorModule)
331 :
332 : // Local Variables:
333 : // c-basic-offset: 2
334 : // End:
|