Line data Source code
1 : /**
2 : * @file CIBModule.cpp
3 : *
4 : * Implementations of CIBModule's functions
5 : *
6 : * This is part of the DUNE DAQ Software Suite, copyright 2020.
7 : * Licensing/copyright details are in the COPYING file that you should have
8 : * received with this code.
9 : */
10 : #include "confmodel/GeoId.hpp"
11 :
12 : // these are the data types in the application model
13 : #include "appmodel/CIBCalibrationStream.hpp"
14 : #include "appmodel/CIBConf.hpp"
15 : #include "appmodel/CIBTrigger.hpp"
16 : #include "appmodel/CIBoardConf.hpp"
17 :
18 : #include "CIBModule.hpp"
19 : #include "CIBModuleIssues.hpp"
20 :
21 : #include "iomanager/IOManager.hpp"
22 : #include "logging/Logging.hpp"
23 :
24 : #include <charconv>
25 : #include <chrono>
26 : #include <cstdint>
27 : #include <map>
28 : #include <queue>
29 : #include <string>
30 : #include <string_view>
31 : #include <thread>
32 : #include <utility>
33 : #include <vector>
34 :
35 : // since cib_data_fmt is shared with the board
36 : // we need this macro to avoid clashes with the namespaces
37 : // not the cleanest solution, but it works and avoids maintaining two separate versions of the same struct
38 : #define CIB_DUNEDAQ 1
39 : // we may need this to help parse the data format arriving from the CIB
40 : #include <cib_data_fmt.h>
41 : #include <cib_utilities.h>
42 : /**
43 : * @brief Name used by TRACE TLOG calls from this source file
44 : */
45 : #define TRACE_NAME "CIBModule" // NOLINT
46 : #define TLVL_ENTER_EXIT_METHODS 10
47 : #define TLVL_CIB_INFO 5
48 : #define TLVL_CIB_DEBUG 10
49 : #define TLVL_CIB_TRACE 15
50 :
51 : // as it happens, the frame structure remains unchanged since NP04
52 : constexpr uint16_t CIB_HSI_FRAME_VERSION = 0x1; // NOLINT
53 : namespace dunedaq::cibmodules {
54 :
55 0 : CIBModule::CIBModule(const std::string& name)
56 : : hsilibs::HSIEventSender(name)
57 0 : , m_is_running(false)
58 0 : , m_is_configured(false)
59 0 : , m_stop_requested(false)
60 0 : , m_receiver_port(8871)
61 0 : , m_receiver_timeout(70000) // 70 ms - we know that triggers will come at 10 Hz max
62 0 : , m_error_state(false)
63 0 : , m_control_ios()
64 0 : , m_control_socket(m_control_ios)
65 0 : , m_control_endpoint()
66 0 : , m_receiver_ios()
67 0 : , m_receiver_socket(m_receiver_ios)
68 0 : , m_thread_(std::bind(&CIBModule::do_hsi_work, this, std::placeholders::_1))
69 0 : , m_calibration_stream_enable(false)
70 0 : , m_calibration_dir("")
71 0 : , m_calibration_prefix("")
72 0 : , m_calibration_file_interval(std::chrono::minutes(15))
73 : // metric utilities
74 0 : , m_num_control_messages_sent(0)
75 0 : , m_num_control_responses_received(0)
76 0 : , m_num_total_triggers_received(0)
77 0 : , m_num_run_triggers_received(0)
78 :
79 0 : , m_trigger_bit(0)
80 0 : , m_receiver_ready(false)
81 : {
82 : // we can infer the instance from the name
83 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Instantiating a cibmodule with argument [" << name << "]";
84 0 : register_command("conf", &CIBModule::do_configure);
85 0 : register_command("start", &CIBModule::do_start);
86 0 : register_command("stop", &CIBModule::do_stop);
87 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Leaving [" << name << "]constructor.";
88 0 : }
89 :
90 0 : CIBModule::~CIBModule()
91 : {
92 0 : if (m_is_running.load()) {
93 0 : const CommandData_t stopobj;
94 :
95 : // const nlohmann::json stopobj;
96 : // this should also take care of closing the streaming socket
97 0 : do_stop(stopobj);
98 0 : }
99 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Closing the control socket " << std::endl;
100 0 : m_control_socket.close();
101 0 : }
102 :
103 : void
104 0 : CIBModule::init(std::shared_ptr<appfwk::ConfigurationManager> cfgMgr)
105 : {
106 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering init() method";
107 :
108 : // init the sender
109 0 : HSIEventSender::init(cfgMgr);
110 :
111 : // assign the local configuration manager to the argument
112 0 : m_cfg = cfgMgr;
113 :
114 : // get the configuration fragment for this module
115 0 : auto mdal = cfgMgr->get_dal<appmodel::CIBModule>(get_name());
116 0 : if (!mdal) {
117 0 : throw cibmodules::CIBConfigFailure(ERS_HERE, "Missing Module configuration for " + get_name());
118 : }
119 :
120 : // assign the module configuration
121 0 : m_module = mdal;
122 :
123 : // setting up connections
124 0 : auto iom = iomanager::IOManager::get();
125 :
126 0 : using hsi_frame_t = dunedaq::hsilibs::HSI_FRAME_STRUCT;
127 0 : for (auto con : m_module->get_outputs()) {
128 0 : if (con->get_data_type() == datatype_to_string<hsi_frame_t>()) {
129 : // Filter connections by UID: only process those with "CIB" or "cib" in the name
130 : // This string comes from the HSISignalWindow UID in the appmodel configuration
131 0 : if ((con->UID().find("CIB") != std::string::npos) || (con->UID().find("cib") != std::string::npos)) {
132 0 : TLOG() << get_name() << ": Setting up HSI Frame output : " << con->UID() << std::endl;
133 0 : m_cib_hsi_data_sender = iom->get_sender<hsi_frame_t>(con->UID());
134 : } else {
135 0 : TLOG_DEBUG(5) << get_name() << ": Skipping output : " << con->UID();
136 : }
137 :
138 : } // if data type is HSI Frame
139 : } // loop over outputs
140 :
141 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting init() method";
142 0 : }
143 :
144 : void
145 0 : CIBModule::do_configure(const CommandData_t&)
146 : {
147 :
148 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering CIB do_configure()";
149 :
150 : // this returns the structure of the CIBConf object
151 0 : auto conf = m_module->get_configuration();
152 : // this gets the CIBoardConf object
153 0 : auto board = m_module->get_board();
154 :
155 0 : auto trigger_conf = conf->get_cib_trigger();
156 :
157 : // identify the trigger bit that this receiver is assigned to
158 : // We need this to construct the HSI frame, right?
159 0 : if (!dunedaq::cibmodules::util::parse_hex(trigger_conf->get_trigger_bit(), m_trigger_bit)) {
160 0 : std::ostringstream msg("");
161 0 : msg << get_name() << ": Unable to parse trigger bit hex string : " << trigger_conf->get_trigger_bit();
162 0 : throw CIBModuleError(ERS_HERE, msg.str());
163 0 : } else {
164 0 : TLOG_DEBUG(TLVL_CIB_DEBUG) << get_name() << ": Parsed trigger bit hex string " << trigger_conf->get_trigger_bit()
165 0 : << " to 0x" << std::hex << m_trigger_bit << std::dec << "["
166 0 : << trigger_conf->get_trigger_id() << "]";
167 : }
168 :
169 : // init monitoring variables
170 0 : m_num_control_messages_sent = 0;
171 0 : m_num_control_responses_received = 0;
172 :
173 : // figure out the identifier of the CIB
174 : // this is set in the configuration
175 0 : auto geo_id = board->get_geo_id();
176 0 : m_det = geo_id->get_detector_id();
177 0 : m_crate = geo_id->get_crate_id();
178 0 : m_slot = geo_id->get_slot_id();
179 :
180 0 : auto session = m_cfg->get_session();
181 :
182 : // init trigger counters
183 0 : m_num_run_triggers_received.store(0);
184 :
185 0 : auto cib_host = conf->get_cib_host();
186 0 : auto cib_port = conf->get_cib_port();
187 :
188 : // network connection to the CIB module
189 0 : boost::asio::ip::tcp::resolver resolver(m_control_ios);
190 0 : boost::asio::ip::tcp::resolver::query query(cib_host, std::to_string(cib_port)); //"np04-iols-cib-02", 8992
191 0 : boost::asio::ip::tcp::resolver::iterator iter = resolver.resolve(query);
192 :
193 0 : m_control_endpoint = iter->endpoint();
194 : // attempt the connection.
195 0 : try {
196 :
197 0 : m_control_socket.connect(m_control_endpoint);
198 0 : m_control_socket.set_option(boost::asio::ip::tcp::no_delay(true));
199 0 : } catch (std::exception& e) {
200 0 : std::ostringstream msg("");
201 0 : msg << get_name() << "Exception caught while establishing connection to CIB : " << e.what();
202 : // do nothing more. Just exist
203 0 : m_is_configured.store(false);
204 0 : throw CIBCommunicationError(ERS_HERE, msg.str());
205 0 : }
206 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Successfully connected to CIB endpoint " << cib_host << ':' << cib_port
207 0 : << std::endl;
208 :
209 : // if necessary, set the calibration stream
210 0 : auto stream_conf = conf->get_calibration_stream();
211 0 : if (stream_conf) {
212 0 : TLOG() << "Calibration stream enabled";
213 0 : m_calibration_stream_enable = true;
214 0 : m_calibration_dir = stream_conf->get_output_directory();
215 0 : m_calibration_file_interval = std::chrono::duration_cast<decltype(m_calibration_file_interval)>(
216 0 : std::chrono::seconds(stream_conf->get_update_period_s()));
217 : }
218 :
219 : // these are for the local receiver operation
220 0 : m_receiver_port = board->get_receiver_port();
221 0 : m_receiver_timeout = std::chrono::milliseconds(conf->get_connection_timeout_ms());
222 0 : m_receiver_host = board->get_receiver_host();
223 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Default board receiver network location (PRELIMINARY) "
224 0 : << m_receiver_host << ':' << m_receiver_port << std::endl;
225 :
226 : // config fragment to be sent to the CIB
227 0 : nlohmann::json config;
228 : //
229 : // if the host is localhost, we need to resolve it to the actual hostname
230 : // and also check whether the port is available
231 : //
232 0 : if (m_receiver_host == "localhost") {
233 0 : boost::asio::ip::tcp::resolver resolver(m_receiver_ios);
234 : // Check if port is already in use, to try to avoid future conflicts
235 0 : unsigned short port = m_receiver_port;
236 0 : while (check_port_in_use(port)) {
237 : // the problem is that if the port is in use, the CIB will be sending the data to the wrong place
238 : // there is little point in continuing
239 0 : std::ostringstream msg("");
240 0 : msg << "Listener port [" << port << "] is in use by someone else. Trying another.";
241 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
242 0 : port++;
243 0 : }
244 0 : if (port != m_receiver_port) {
245 0 : std::ostringstream msg("");
246 0 : msg << "Listener port [" << m_receiver_port << "] is in use. Relocating to port [" << port << "]";
247 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
248 0 : m_receiver_port = port;
249 0 : } else {
250 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Listener port " << m_receiver_port << " is available." << std::endl;
251 : }
252 :
253 : // at this we have to find the hostname to tell the board where to send the data
254 0 : boost::asio::ip::tcp::resolver::query query_for_local(boost::asio::ip::host_name(), "");
255 0 : iter = resolver.resolve(query_for_local);
256 :
257 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Resolved localhost to " << iter->endpoint().address().to_string()
258 0 : << std::endl;
259 0 : m_receiver_host = iter->endpoint().address().to_string();
260 :
261 : // create the json string out of the config fragment
262 : // replacing the receiver address with the one that we just calculated
263 0 : try {
264 0 : config = board->get_cib_json(*session, m_receiver_host, m_receiver_port);
265 0 : } catch (nlohmann::json::exception& e) {
266 0 : std::ostringstream msg("");
267 0 : msg << get_name() << "Caught a JSON exception converting config fragment : " << e.what();
268 0 : m_is_configured.store(false);
269 0 : throw CIBModuleError(ERS_HERE, msg.str());
270 0 : } catch (std::exception& e) {
271 0 : std::ostringstream msg("");
272 0 : msg << get_name() << "Caught STD exception while converting config fragment : " << e.what();
273 : // do nothing more. Just exist
274 0 : m_is_configured.store(false);
275 0 : throw CIBModuleError(ERS_HERE, msg.str());
276 0 : }
277 0 : } else {
278 : /* just use the configuration information */
279 : // nlohmann::to_json(config, board->get_cib_json(*session));
280 0 : config = board->get_cib_json(*session);
281 : }
282 0 : auto json_dump = config.dump();
283 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << "Sending configuration: [" << json_dump << "] to CIB board";
284 0 : send_config(config.dump());
285 0 : m_is_configured.store(true);
286 0 : }
287 :
288 : void
289 0 : CIBModule::do_start(const CommandData_t& startobj)
290 : {
291 :
292 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_start() method";
293 : // actually, the first thing to check is whether the CIB has been configured
294 : // if not, this won't work
295 0 : if (!m_is_configured.load()) {
296 0 : throw CIBWrongState(ERS_HERE, "CIB has not been successfully configured.");
297 : }
298 :
299 : // Set this to false early so it doesn't interfere with the start
300 0 : m_stop_requested.store(false);
301 0 : m_run_number.store(startobj.at("run").get<daqdataformats::run_number_t>());
302 : // reset the metrics/counters
303 0 : m_num_run_triggers_received.store(0);
304 :
305 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Sending start of run command with run number " << m_run_number.load();
306 0 : m_thread_.start_working_thread();
307 :
308 : // NFB: There was a potential race condition here: the socket in the working thread
309 : // needs to be in place before the CIB receives order to send data, or we risk having a connection
310 : // failure, if for some reason the CIB attempts to connect before the working thread is ready to receive.
311 0 : if (m_calibration_stream_enable) {
312 0 : std::stringstream run;
313 0 : run << "run" << m_run_number.load();
314 0 : set_calibration_stream(run.str());
315 0 : }
316 : int cnt = 0;
317 0 : while (!m_receiver_ready.load()) {
318 0 : std::this_thread::sleep_for(std::chrono::milliseconds(10));
319 0 : cnt++;
320 0 : if (cnt > 50) {
321 : // the socket didn't get ready on time
322 0 : throw CIBModuleError(ERS_HERE, "Receiver socket timed out before becoming ready.");
323 : }
324 : }
325 0 : TLOG_DEBUG(TLVL_CIB_DEBUG) << get_name() << ": All ready to signal the CIB to start";
326 :
327 0 : nlohmann::json cmd;
328 0 : cmd["command"] = "start_run";
329 0 : cmd["run_number"] = m_run_number.load();
330 :
331 0 : if (send_message(cmd.dump())) {
332 0 : m_is_running.store(true);
333 0 : TLOG() << get_name() << ": CIB run started successfully";
334 : } else {
335 0 : throw CIBCommunicationError(ERS_HERE, "Unable to start CIB run");
336 : }
337 :
338 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_start() method";
339 0 : }
340 :
341 : void
342 0 : CIBModule::do_stop(const CommandData_t& /*stopobj*/)
343 : {
344 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_stop() method";
345 0 : TLOG_DEBUG(TLVL_CIB_DEBUG) << get_name() << ": Sending stop run command" << std::endl;
346 :
347 : // Set stop flag BEFORE sending command so receiver thread knows to expect EOF
348 0 : m_stop_requested.store(true);
349 :
350 0 : if (send_message("{\"command\":\"stop_run\"}")) {
351 : // Response arrival means CIB has closed its data socket (see Handler::stop_run())
352 : // Receiver thread will detect EOF and exit cleanly
353 0 : std::this_thread::sleep_for(std::chrono::milliseconds(2));
354 0 : TLOG() << get_name() << ": CIB run stopped successfully";
355 0 : m_is_running.store(false);
356 : } else {
357 : // failed to sent the message to stop the run.
358 : // stop the collecting thread and then throw, since that
359 : // attempts a cleaner exit
360 0 : m_thread_.stop_working_thread();
361 :
362 0 : throw CIBCommunicationError(ERS_HERE, "Unable to stop CIB");
363 : }
364 : //
365 0 : m_thread_.stop_working_thread();
366 :
367 : // -- print the counters for local info
368 0 : TLOG() << get_name() << ": CIB trigger counter summary after run [" << m_run_number << "]:\n\n"
369 0 : << "IOLS trigger counter in run : " << m_num_run_triggers_received << "\n"
370 0 : << "Global IOLS trigger count : " << m_num_total_triggers_received << std::endl;
371 :
372 : // reset counters
373 0 : m_num_run_triggers_received = 0;
374 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_stop() method";
375 0 : }
376 :
377 : // this is where most of the work is really done
378 : void
379 0 : CIBModule::do_hsi_work(std::atomic<bool>& running_flag)
380 : {
381 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Entering do_work() method";
382 :
383 0 : std::size_t n_bytes = 0;
384 0 : std::size_t n_words = 0;
385 0 : std::size_t prev_seq = 0;
386 0 : bool first = true;
387 :
388 : // connect to socket
389 : // should we keep everything local or under the class?
390 0 : boost::system::error_code ec;
391 :
392 : // check that this port is still available
393 0 : if (check_port_in_use(m_receiver_port)) {
394 : // the problem is that, at this stage, if the port is in use,
395 : // the CIB will be sending the data to the wrong place
396 : // there is little point in continuing
397 0 : std::ostringstream msg("");
398 0 : msg << "Listener port [" << m_receiver_port << "] is in use by someone else. Failing.";
399 0 : ers::error(CIBMessage(ERS_HERE, msg.str()));
400 0 : }
401 :
402 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Will set up the listener on port " << m_receiver_port << std::endl;
403 0 : boost::asio::ip::tcp::acceptor acceptor(m_receiver_ios,
404 0 : boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), m_receiver_port));
405 :
406 0 : acceptor.listen(boost::asio::ip::tcp::socket::max_connections, ec);
407 0 : if (ec) {
408 0 : std::ostringstream msg("");
409 0 : msg << get_name() << ": CIB got an error listening on socket: :" << m_receiver_port << " -- reason: '" << ec << "'";
410 0 : throw CIBCommunicationError(ERS_HERE, msg.str());
411 : return;
412 0 : } else {
413 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Waiting for an incoming connection on port " << m_receiver_port
414 0 : << std::endl;
415 : }
416 :
417 0 : std::future<void> accepting = async(std::launch::async, [&] { acceptor.accept(m_receiver_socket, ec); });
418 0 : if (ec) {
419 0 : std::stringstream msg;
420 0 : msg << "Socket opening failed:: " << ec.message();
421 0 : ers::error(CIBCommunicationError(ERS_HERE, msg.str()));
422 0 : return;
423 0 : }
424 : //
425 0 : m_receiver_ready.store(true);
426 :
427 0 : while (running_flag.load() && !m_stop_requested.load()) {
428 0 : if (accepting.wait_for(m_receiver_timeout) == std::future_status::ready) {
429 : break;
430 : }
431 : }
432 :
433 0 : TLOG() << get_name() << ": Connection received: start reading" << std::endl;
434 :
435 : // -- A couple of variables to help in the data parsing
436 : /**
437 : * The structure is a bit different than in the CTB
438 : * The TCP packet contains a single word (the trigger)
439 : * But other than that, everything are triggers
440 : */
441 :
442 0 : dunedaq::cib::daq::iols_tcp_packet_t tcp_packet;
443 0 : TLOG_DEBUG(TLVL_CIB_TRACE) << "Checking expected sizes: "
444 0 : << " sizeof(iols_tcp_packet_t)=" << sizeof(dunedaq::cib::daq::iols_tcp_packet_t)
445 0 : << " sizeof(iols_trigger_t)=" << sizeof(dunedaq::cib::daq::iols_trigger_t)
446 0 : << " sizeof(tcp_header_t)=" << sizeof(dunedaq::cib::daq::tcp_header_t) << std::endl;
447 :
448 : // boost::system::error_code receiving_error;
449 0 : bool connection_closed = false;
450 :
451 0 : while (running_flag.load() && !m_stop_requested.load()) {
452 0 : update_calibration_file();
453 :
454 0 : if (!read(m_receiver_socket, tcp_packet)) {
455 0 : connection_closed = true;
456 0 : break;
457 : }
458 :
459 0 : n_bytes = tcp_packet.header.packet_size;
460 0 : n_words = n_bytes / sizeof(dunedaq::cib::daq::iols_trigger_t);
461 :
462 0 : if (n_words != 1) {
463 0 : std::ostringstream msg("");
464 0 : msg << "Received more than one IoLS trigger word at once! This should never happen. Got " << n_bytes
465 0 : << " (expected " << sizeof(dunedaq::cib::daq::iols_trigger_t) << ")";
466 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
467 0 : }
468 : // the CIB only ships one word per packet....so this error should be impossible
469 : // check continuity of the sequence numbers
470 0 : if (first) {
471 : // first word being fetched. The sequence number should be zero
472 0 : if (tcp_packet.header.sequence_id != 0) {
473 0 : std::ostringstream msg("");
474 0 : msg << "Missing sequence. First word should have sequence number 0. Got "
475 0 : << static_cast<int>(tcp_packet.header.sequence_id);
476 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
477 0 : }
478 : first = false;
479 : } else {
480 0 : bool failed = false;
481 : // in case it rolled over, compare to 255
482 0 : if (tcp_packet.header.sequence_id == 0) {
483 0 : if (prev_seq != 255) {
484 : failed = true;
485 : }
486 : } else {
487 0 : if (tcp_packet.header.sequence_id != (prev_seq + 1)) {
488 : failed = true;
489 : }
490 : }
491 : if (failed) {
492 0 : std::ostringstream msg("");
493 0 : msg << "Skipped CIB word sequence. Prev word " << prev_seq << " current word "
494 0 : << static_cast<int>(tcp_packet.header.sequence_id);
495 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
496 0 : }
497 : }
498 0 : prev_seq = tcp_packet.header.sequence_id;
499 :
500 0 : update_buffer_counts(n_words);
501 :
502 : // temporarily print the trigger
503 : // I leave this here for debugging purposes, but don“t even keep it in the logs, since it is too verbose. We can
504 : // always add it back if we need to debug something TLOG_DEBUG(TLVL_CIB_TRACE) << "TRIGGER : ts " <<
505 : // tcp_packet.word.timestamp
506 : // << " pos_m1 " << util::get_m1(tcp_packet.word)
507 : // << " pos_m2 " << util::get_m2(tcp_packet.word)
508 : // << " pos_m3 " << util::get_m3(tcp_packet.word);
509 :
510 0 : if (m_calibration_stream_enable) {
511 0 : m_calibration_file.write(reinterpret_cast<const char*>(&tcp_packet.word), sizeof(tcp_packet.word)); // NOLINT
512 0 : m_calibration_file.flush();
513 : } // word printing in calibration stream
514 :
515 0 : TLOG_DEBUG(TLVL_CIB_DEBUG) << get_name() << "Received IoLS trigger word!";
516 0 : ++m_num_total_triggers_received;
517 0 : ++m_num_run_triggers_received;
518 :
519 : // we do not need to know anything else
520 : // ideally, one could add other information such as the direction
521 : // this should be coming packed in the trigger word
522 : // note, however, that to reconstruct the trace direction we also would need the source position
523 : // and that we cannot afford to send, so we can just make it up out of the IoLS system
524 : //
525 : // Send HSI data to a DLH
526 0 : std::array<uint32_t, 7> hsi_struct;
527 0 : hsi_struct[0] = (0x1 << 26) | // some random bit that could indicate the type of frame
528 0 : (m_slot << 22) | // slot number
529 0 : (m_crate << 12) | // crate number
530 0 : (m_det << 6) | // detector number
531 : CIB_HSI_FRAME_VERSION;
532 : // timestamps - I like the explicit masking here to mistakes
533 0 : hsi_struct[1] = tcp_packet.word.timestamp & 0xFFFFFFFF; // ts low
534 0 : hsi_struct[2] = (tcp_packet.word.timestamp >> 32) & 0xFFFFFFFF; // ts high
535 :
536 : // we shall use these 2 sets of 32 bits to define the periscope position
537 : // pos_m3 == linear stage
538 0 : hsi_struct[3] = dunedaq::cibmodules::util::get_m3(tcp_packet.word); // lower 32b 0
539 : // pos_m3 == RNN600
540 0 : hsi_struct[4] = dunedaq::cibmodules::util::get_m2(tcp_packet.word); // upper 32b
541 : /**
542 : * A note about the 5th entry
543 : * The trigger bit is actually mapped into a single bit, that is then remapped back
544 : * into an index
545 : */
546 0 : hsi_struct[5] = m_trigger_bit; // trigger_map;
547 0 : hsi_struct[6] = m_num_run_triggers_received.load(); // m_generated_counter;
548 :
549 : // Same thing here. If something really bad happens, this code can be very useful, but we do not want to print this
550 : // in the logs, unless strictly necessary, since it is too verbose. TLOG_DEBUG(TLVL_CIB_TRACE) << "CIB HSI Frame: "
551 : // << "0x" << std::hex << hsi_struct[0]
552 : // << ", 0x" << hsi_struct[1]
553 : // << ", 0x" << hsi_struct[2]
554 : // << ", 0x" << hsi_struct[3]
555 : // << ", 0x" << hsi_struct[4]
556 : // << ", 0x" << hsi_struct[5]
557 : // << ", 0x" << hsi_struct[6]
558 : // << std::dec << std::endl;
559 :
560 0 : if (!m_cib_hsi_data_sender) {
561 0 : std::ostringstream msg("");
562 0 : msg << "HSI Data Sender not properly configured! This will go down in flames.";
563 0 : ers::error(CIBCommunicationError(ERS_HERE, msg.str()));
564 : // we cannot continue
565 0 : break;
566 0 : }
567 :
568 0 : send_raw_hsi_data(hsi_struct, m_cib_hsi_data_sender.get());
569 :
570 : // TODO Nuno Barros Apr-02-2024 : properly fill device id when someone explains me
571 : // how to get it
572 : // in fact, this may disappear in the future where the CIB is decoupled from
573 : // the HSI infrastructure
574 0 : dfmessages::HSIEvent event(
575 0 : m_det, m_trigger_bit, tcp_packet.word.timestamp, m_num_run_triggers_received.load(), m_run_number);
576 :
577 0 : send_hsi_event(event);
578 0 : if (connection_closed) {
579 : break;
580 : }
581 : }
582 :
583 : // Wait for stop signal (which only comes after CIB closed its sender socket)
584 0 : while (m_is_running.load()) {
585 0 : std::this_thread::sleep_for(std::chrono::microseconds(100));
586 : }
587 :
588 0 : boost::system::error_code closing_error;
589 : // if the system is already in an error state
590 : // we should call for a socket shutdown to force
591 : // the connection to close
592 0 : if (m_error_state.load()) {
593 :
594 0 : m_receiver_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, closing_error);
595 :
596 0 : if (closing_error) {
597 0 : std::stringstream msg;
598 0 : msg << "Error in shutdown " << closing_error.message();
599 0 : ers::error(CIBCommunicationError(ERS_HERE, msg.str()));
600 0 : }
601 : }
602 :
603 0 : m_receiver_socket.close(closing_error);
604 :
605 0 : if (closing_error) {
606 0 : std::stringstream msg;
607 0 : msg << "Socket closing failed:: " << closing_error.message();
608 0 : ers::error(CIBCommunicationError(ERS_HERE, msg.str()));
609 0 : }
610 :
611 0 : m_receiver_ready.store(false);
612 :
613 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": End of do_work loop: stop receiving data from the CIB";
614 :
615 0 : TLOG_DEBUG(TLVL_ENTER_EXIT_METHODS) << get_name() << ": Exiting do_work() method";
616 0 : }
617 :
618 : template<typename T>
619 : bool
620 0 : CIBModule::read(boost::asio::ip::tcp::socket& socket, T& obj)
621 : {
622 :
623 0 : boost::system::error_code receiving_error;
624 0 : boost::asio::read(socket, boost::asio::buffer(&obj, sizeof(T)), receiving_error);
625 :
626 0 : if (!receiving_error) {
627 : return true;
628 : }
629 :
630 0 : if (receiving_error == boost::asio::error::eof) {
631 0 : if (m_stop_requested.load()) {
632 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Socket closed after stop request." << std::endl;
633 : } else {
634 0 : std::string error_message = "Socket closed: " + receiving_error.message();
635 0 : ers::error(CIBCommunicationError(ERS_HERE, error_message));
636 0 : }
637 :
638 0 : return false;
639 : }
640 :
641 : if (receiving_error) {
642 0 : std::string error_message = "Read failure: " + receiving_error.message();
643 0 : ers::error(CIBCommunicationError(ERS_HERE, error_message));
644 : return false;
645 0 : }
646 :
647 : return true;
648 : }
649 :
650 : void
651 0 : CIBModule::init_calibration_file()
652 : {
653 0 : if (!m_calibration_stream_enable) {
654 0 : return;
655 : }
656 0 : char file_name[200] = "";
657 0 : time_t rawtime;
658 0 : time(&rawtime);
659 0 : struct tm local_tm;
660 0 : struct tm* timeinfo = localtime_r(&rawtime, &local_tm);
661 0 : strftime(file_name, sizeof(file_name), "%F_%H.%M.%S.iols.calib", timeinfo);
662 0 : std::string global_name = m_calibration_dir + m_calibration_prefix + file_name;
663 0 : m_calibration_file.open(global_name, std::ofstream::binary);
664 0 : if (!m_calibration_file.is_open()) {
665 0 : std::ostringstream msg;
666 0 : msg << get_name() << ": Unable to open calibration stream file: " << global_name;
667 0 : ers::warning(CIBMessage(ERS_HERE, msg.str()));
668 0 : m_calibration_stream_enable = false;
669 0 : return;
670 0 : }
671 0 : m_last_calibration_file_update = std::chrono::steady_clock::now();
672 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": New Calibration Stream file: " << global_name << std::endl;
673 0 : }
674 :
675 : void
676 0 : CIBModule::update_calibration_file()
677 : {
678 :
679 0 : if (!m_calibration_stream_enable) {
680 0 : return;
681 : }
682 :
683 0 : std::chrono::steady_clock::time_point check_point = std::chrono::steady_clock::now();
684 :
685 0 : if (check_point - m_last_calibration_file_update < m_calibration_file_interval) {
686 : return;
687 : }
688 :
689 0 : m_calibration_file.close();
690 0 : init_calibration_file();
691 : }
692 :
693 : bool
694 0 : CIBModule::set_calibration_stream(const std::string& prefix)
695 : {
696 :
697 0 : if (m_calibration_dir.back() != '/') {
698 0 : m_calibration_dir += '/';
699 : }
700 0 : m_calibration_prefix = prefix;
701 0 : if (prefix.size() > 0) {
702 0 : m_calibration_prefix += '_';
703 : }
704 : // possibly we could check here if the directory is valid and writable before assuming the calibration stream is
705 : // valid
706 0 : return true;
707 : }
708 :
709 : void
710 0 : CIBModule::send_config(const std::string& config)
711 : {
712 :
713 0 : TLOG_DEBUG(TLVL_CIB_INFO) << get_name() << ": Sending config" << std::endl;
714 :
715 : // structure the message to have a common management structure
716 0 : nlohmann::json conf;
717 0 : conf["command"] = "config";
718 0 : conf["config"] = nlohmann::json::parse(config);
719 :
720 0 : TLOG_DEBUG(TLVL_CIB_DEBUG) << get_name() << ": Shipped config : " << conf.dump() << std::endl;
721 :
722 0 : if (send_message(conf.dump())) {
723 0 : m_is_configured.store(true);
724 : } else {
725 0 : throw CIBCommunicationError(ERS_HERE, "Unable to configure CIB");
726 : }
727 0 : }
728 :
729 : bool
730 0 : CIBModule::send_message(const std::string& msg)
731 : {
732 :
733 : // add error options
734 0 : boost::system::error_code error;
735 0 : TLOG_DEBUG(1) << get_name() << ": Sending message: " << msg;
736 :
737 0 : m_num_control_messages_sent++;
738 :
739 0 : boost::asio::write(m_control_socket, boost::asio::buffer(msg), error);
740 0 : boost::array<char, 1024> reply_buf{ " " };
741 0 : m_control_socket.read_some(boost::asio::buffer(reply_buf), error);
742 0 : std::stringstream raw_answer(std::string(reply_buf.begin(), reply_buf.end()));
743 0 : TLOG_DEBUG(1) << get_name() << ": Unformatted answer: " << raw_answer.str();
744 :
745 0 : nlohmann::json answer;
746 0 : raw_answer >> answer;
747 0 : nlohmann::json& messages = answer["feedback"];
748 0 : TLOG_DEBUG(1) << get_name() << ": Received messages: " << messages.size();
749 :
750 0 : bool ret = true;
751 0 : for (nlohmann::json::size_type i = 0; i != messages.size(); ++i) {
752 :
753 0 : m_num_control_responses_received++;
754 :
755 0 : std::string type = messages[i]["type"].dump();
756 0 : if (type.find("error") != std::string::npos || type.find("Error") != std::string::npos ||
757 0 : type.find("ERROR") != std::string::npos) {
758 0 : ers::error(CIBMessage(ERS_HERE, messages[i]["message"].dump()));
759 0 : ret = false;
760 0 : } else if (type.find("warning") != std::string::npos || type.find("Warning") != std::string::npos ||
761 0 : type.find("WARNING") != std::string::npos) {
762 0 : ers::warning(CIBMessage(ERS_HERE, messages[i]["message"].dump()));
763 0 : } else if (type.find("info") != std::string::npos || type.find("Info") != std::string::npos ||
764 0 : type.find("INFO") != std::string::npos) {
765 0 : TLOG() << "Message from the CIB : " << messages[i]["message"].dump();
766 : } else {
767 0 : std::stringstream blob;
768 0 : blob << messages[i];
769 0 : TLOG() << get_name() << ": Unformatted feedback from the board: " << blob.str();
770 0 : }
771 0 : }
772 0 : return ret;
773 0 : }
774 :
775 : void
776 0 : CIBModule::update_buffer_counts(uint new_count) // NOLINT(build/unsigned)
777 : {
778 0 : std::unique_lock mon_data_lock(m_buffer_counts_mutex);
779 0 : if (m_buffer_counts.size() > 1000) {
780 0 : m_buffer_counts.pop_front();
781 : }
782 0 : m_buffer_counts.push_back(new_count);
783 0 : }
784 :
785 : double
786 0 : CIBModule::read_average_buffer_counts()
787 : {
788 0 : std::unique_lock mon_data_lock(m_buffer_counts_mutex);
789 :
790 0 : double total_counts;
791 0 : uint32_t number_of_counts; // NOLINT(build/unsigned)
792 :
793 0 : total_counts = 0;
794 0 : number_of_counts = m_buffer_counts.size();
795 :
796 0 : if (number_of_counts) {
797 0 : for (uint i = 0; i < number_of_counts; ++i) { // NOLINT(build/unsigned)
798 0 : total_counts = total_counts + m_buffer_counts.at(i);
799 : }
800 0 : return total_counts / number_of_counts;
801 : } else {
802 : return 0;
803 : }
804 0 : }
805 :
806 : void
807 0 : CIBModule::generate_opmon_data()
808 : {
809 0 : dunedaq::cibmodules::opmon::CIBModuleInfo module_info;
810 :
811 0 : module_info.set_num_control_messages_sent(m_num_control_messages_sent.load());
812 0 : module_info.set_num_control_responses_received(m_num_control_responses_received.load());
813 0 : module_info.set_hardware_running(m_is_running.load());
814 0 : module_info.set_hardware_configured(m_is_configured.load());
815 0 : module_info.set_num_total_triggers_received(m_num_total_triggers_received.load());
816 0 : module_info.set_num_run_triggers_received(m_num_run_triggers_received.load());
817 :
818 : // -- need to define these counters (and set the code to update them
819 0 : module_info.set_sent_hsi_events_counter(m_sent_counter.load());
820 0 : module_info.set_failed_to_send_hsi_events_counter(m_failed_to_send_counter.load());
821 : // module_info.set_last_sent_timestamp(m_last_sent_timestamp.load());
822 0 : module_info.set_average_buffer_occupancy(read_average_buffer_counts());
823 :
824 0 : publish(std::move(module_info));
825 :
826 : // should we also publish specific trigger info?
827 : // doesn't seem necessary at this time
828 0 : }
829 :
830 : bool
831 0 : CIBModule::check_port_in_use(unsigned short port)
832 : {
833 0 : using namespace boost::asio;
834 0 : using ip::tcp;
835 :
836 0 : io_service svc;
837 0 : tcp::acceptor a(svc);
838 :
839 0 : boost::system::error_code ec;
840 0 : a.open(tcp::v4(), ec) || a.bind({ tcp::v4(), port }, ec);
841 0 : a.close();
842 0 : return ec == error::address_in_use;
843 0 : }
844 : } // namespace dunedaq::cibmodules
845 :
846 0 : DEFINE_DUNE_DAQ_MODULE(dunedaq::cibmodules::CIBModule)
|