Skip to content

Commit 790effb

Browse files
authored
Merge pull request #10 from rfoxkendo/upstream-merge-test
Provide fribdaq-readout to upstream repository.
2 parents 55f6f19 + 65f914b commit 790effb

12 files changed

+1852
-0
lines changed

extras/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_subdirectory(mini-daq)
88
add_subdirectory(mvlc-ctrl-tests)
99
add_subdirectory(dev-tools)
1010
add_subdirectory(mvlc-cli)
11+
add_subdirectory(fribdaq)
1112

1213
if (UNIX AND NOT APPLE)
1314
add_executable(netlink-test netlink-socket-mem-monitoring/netlink-test.cc)

extras/fribdaq/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#cmake_minimum_required(VERSION 3.13)
2+
#project(fribdaq-readout)
3+
4+
# NSCLDAQ_ROOT - defines where the NSCLDAQ software is installed
5+
# MVLC_ROOT - Defines where the Mesytec mvlc software is installed.
6+
7+
8+
add_subdirectory(src)

extras/fribdaq/Readme.md

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
### Introduction
2+
3+
This directory supplies an optional program, fribdaq-readout that is based on minidaq, but can write data to an FRIB/NSCLDAQ ring buffer. To enable building and installing the program you must add
4+
5+
```
6+
-DNSCLDAQ_ROOT=nscldaqroot
7+
```
8+
9+
where ```nscldaqroot``` is the top level directory of the FRIB/NSCLDAQ installation you want this to link against. The softare was tested with FRIB/NSCLDAQ version ```12.1-007```, however it should operate with any version at ```11.0``` or above.
10+
11+
when the full mvlc support software is built, the installation ```bin``` directory will include the program ```fribdaq-readout```
12+
13+
14+
### Using fribdaq-readout
15+
16+
```fribdaq-readout``` supports the options supported by minidaq
17+
(use --help to list the full option set and short form documentation) with the added options:
18+
19+
* ```--ring ringname``` selects the name of the ring buffer to which the data should be written. This is a ring name not a URI, as only local ringbuffers can be written to. If the option is not provided, this defaults to the name of the logged in user.
20+
* ```--sourceid id``` When used in a larger system that builds events from several sources using the FRIB/NSCLDAQ event builder, this specifies the unique integer source id that will be used to identify fragments from this data source. If not provided, this defaults to 0.
21+
* ```--timestamp-library``` When used with a larger system that builds events from several sources using the FRIB/NSCLDAQ event builder, this specifies a shared library file which includes code to extract timestamps from the raw event data. If not provided, the body headers required to build events will not be included in the ```PHYSCIS_EVENT``` items.
22+
23+
Following all options on the command line, a single positional parameter provides the name of a .yaml configuration file that was either exported from ```mdaq``` or produced using the FRIBDA/NSCLDAQ configuration tools (to be included in versions 12.2 of FRIB/NSCLDAQ).
24+
25+
#### fribdaq-readout commands
26+
27+
Once the program has been started successfully, it will enter a command processing loop. This loop recognizes the following, very simple commands, which can be provided in either lower case or upper case (but not [yet] mixed case):
28+
29+
* ```SETRUN run-number``` sets the number of the next run. An positive integer *run-number* is the sole parameter of this command.
30+
This command is only accepted if the run is halted.
31+
* ```TITLE The title text``` sets the title to be used for the next run. The remainder of the command line will be used as the title. This command is only accepted if the run is halted.
32+
* ```BEGIN``` starts taking data (begins a new run). A data format item and a BEGIN state change item are emitted to the ringbuffer prior to any other data. This command is only accepte if the run is halted.
33+
* ```PAUSE``` Pauses an active run. After pausing, a format item and a PAUSE state change item are emitted to the ringbuffer.
34+
* ```RESUME``` Resumes a paused run. Before resuming, a format item
35+
and a RESUME state change item are emitted to the ringbuffer. This command is only accepted if the run is paused.
36+
* ```END``` Ends a run. This command is only acceeptd if the run is active or paused. When the run is ended, a format item and END state change item will be emitted to the ringbuffer.
37+
38+
39+
#### Creating setups with mdaq.
40+
41+
The fribdaq-readout program makes some assumptions about the configuration it has been given:
42+
43+
* Stack 0 in mdaq is assumed to be PHYSICS_EVENT data. It will generate PHYSICS_EVENT ring items.
44+
* Stack 1 is assumed to be data from periodic scalers. It will generate PERIODIC_SCALER ring items.
45+
46+
FRIB/NSCLDAQ setup software will generate appropriate stack configurations with the event trigger a pulse in NIM1.
47+
48+
Note that if you use mdaq you should remove the MCST sections of the configurations as multiple instances will cause start errors at this time.
49+
50+
#### Using fribdaq-readout with the FRIB/NSCLDAQ event builder.
51+
52+
To use the fribdaq-reaout with the FRIB/NSCLDAQ event builder, you must supply code to extract timestamps from the data. This code is dynamically loaded from a shared object. The shared library must have a C bindings entry point named ```extract_timestamp```
53+
that returns a uint64 timestamp. Input parameters are:
54+
* unsigned nmodules The number of module data structs in event below,
55+
* const mesytec::mvlc::readout_parser::ModuleData* event a pointer to the first module data in the event.
56+
57+
Here is a sample timestamp extractor that just returns an incrementing trigger number:
58+
59+
```c++
60+
#include <fribdaq/parser_callbacks.h>
61+
#include <stdint.h>
62+
// Timestamp extractors must have C linkage:
63+
extern "C" {
64+
/** Sample timestamp extractor
65+
normal extractors need to look at the data which are
66+
in the module structs
67+
*/
68+
uint64_t
69+
extract_timestamp(unsigned numModules, const mesytec::mvlc::readout_parser::ModuleData* event) {
70+
static uint64_t timestamp = 0;
71+
72+
return timestamp++;
73+
}
74+
}
75+
```
76+
77+
* Note the use of extern "C" to force the bindings to C rather than C++ bindings. If you actually compile your extractor in C you should not do this.
78+
* The parser_callbacks.h provides definitions need by the parser callbacks and also pulls in the Mesytec headers that define the ModulData structure. Module data supplies the data for each module in the mdaq configuration in order from first to last. It has a few fields but the ones you usually care about are:
79+
* size - which is th number of uint32_t items in the data for this module
80+
* data - a void pointer to the data read from the module.
81+
82+
In a typical actual extractor the first 64 bits of the first module will typically be the timestamp. so the last line of the example might be:
83+
```c++
84+
...
85+
const uint64_t* pTs = reinterpret_cast<const uint64_t*>(event->data);
86+
return *pTs;
87+
...
88+
```
89+
90+
#### Event format.
91+
92+
Recall that stack 0 is used by fribdaq-readout for event data. It is packaged in a ringbuffer using a normal CPhysicsEventItem. If a timestamp extractor is provided, the item will have a body header. If not it won't. The body of the event will consist just of the data from each ModuleData packed end-to-end in the payload of the ring item.
93+
94+
Here is the code fragment that creates and submits ring items:
95+
96+
```c++
97+
98+
...
99+
std::unique_ptr<CPhysicsEventItem> pEvent;
100+
if (context->s_tsExtractor) {
101+
pEvent.reset(new CPhysicsEventItem(
102+
context->s_tsExtractor(moduleCount, pModuleDataList), context->s_sourceid, 0,
103+
eventSize + 100
104+
));
105+
} else {
106+
pEvent.reset(new CPhysicsEventItem(eventSize + 100));
107+
}
108+
CPhysicsEventItem& event(*pEvent);
109+
110+
for ( int i = 0; i < moduleCount; i++) {
111+
uint32_t* pCursor = reinterpret_cast<uint32_t*>(event.getBodyCursor());
112+
auto size = pModuleDataList->data.size;
113+
memcpy(pCursor, pModuleDataList->data.data, size * sizeof(uint32_t));
114+
pCursor += size;
115+
event.setBodyCursor(pCursor);
116+
}
117+
event.updateSize();
118+
119+
event.commitToRing(*(context->s_pRing));
120+
...
121+
```
122+
123+
where context is a struct that contains information like the source id, the ring item, and the timestamp extractor function pointer.
124+
125+
See the FRIB/NSCLDAQ documentation for CPhysicsEventitem for more information.
126+
127+
The work of putting data into the ring item body is done in the for loop which iterates over the modules ModuleData items passed in and copies the data associated with each module into the ring item body. Note that the size of the body can be gotten from a reconstituted object by calling the object's getBodySize method.
128+
129+

extras/fribdaq/src/CMakeLists.txt

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
set(NSCLDAQ_ROOT "_" CACHE STRING "_")
3+
4+
5+
# Only build any of this stuff if NSCLDAQ_ROOT has been defined.
6+
#
7+
8+
if (NSCLDAQ_ROOT STREQUAL "_") # not defined.
9+
10+
else ()
11+
12+
add_executable(fribdaq-readout
13+
fribdaq_readout.cc
14+
command_parse.cc
15+
username.cc
16+
parser_callbacks.cc
17+
)
18+
target_include_directories(
19+
fribdaq-readout PRIVATE
20+
${CMAKE_CURRENT_SOURCE_DIR}
21+
${NSCLDAQ_ROOT}/include
22+
)
23+
target_link_libraries(fribdaq-readout
24+
PRIVATE mesytec-mvlc
25+
PRIVATE BFG::Lyra
26+
PRIVATE spdlog::spdlog
27+
PRIVATE ${NSCLDAQ_ROOT}/lib/libdataformat.so
28+
PRIVATE ${NSCLDAQ_ROOT}/lib/liburl.so
29+
PRIVATE ${NSCLDAQ_ROOT}/lib/libdaqshm.so
30+
PRIVATE ${NSCLDAQ_ROOT}/lib/libDataFlow.so
31+
PRIVATE ${NSCLDAQ_ROOT}/lib/libException.so
32+
)
33+
target_link_options(fribdaq-readout PRIVATE
34+
-ldl -Wl,-rpath=${NSCLDAQ_ROOT}/lib
35+
)
36+
install(TARGETS fribdaq-readout)
37+
38+
# parser_callbacks.h must be installed.
39+
# We'll put it in ${CMAKE_INSTALL_INCLUDEDIR}/fribdaq
40+
41+
set_target_properties(fribdaq-readout PROPERTIES PUBLIC_HEADER parser_callbacks.h)
42+
install(TARGETS fribdaq-readout
43+
PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/fribdaq"
44+
)
45+
46+
# Tests:
47+
48+
add_executable(parse_test parse_test.cc command_parse.cc)
49+
target_link_libraries(parse_test
50+
PRIVATE gtest
51+
PRIVATE gtest_main
52+
)
53+
add_test(NAME parse_test COMMAND parse_test)
54+
endif ()
55+
56+
57+

extras/fribdaq/src/command_parse.cc

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* @file command_parse.cc
3+
* @brief interface for stupid command parser.
4+
* @author Ron Fox <fox @ frib dot msu dot edu>
5+
*
6+
*
7+
* This software is Copyright by the Board of Trustees of Michigan
8+
* State University (c) Copyright 2025.
9+
*
10+
* You may use this software under the terms of the GNU public license
11+
* (GPL). The terms of this license are described at:
12+
*
13+
* http://www.gnu.org/licenses/gpl.txt
14+
*
15+
* @note - this file is private to fribdaq's readout
16+
* @note - this is extracted from fribdaq_readout.cc to allow unit tests to be run
17+
* against it.
18+
*/
19+
#include "command_parse.h"
20+
#include <sstream>
21+
#include <algorithm>
22+
#include <ctype.h>
23+
#include <map>
24+
25+
26+
// Map between command words and enum
27+
static std::map<std::string, StdinCommands> commandMap = {
28+
{"BEGIN", BEGIN}, {"begin", BEGIN},
29+
{"EXIT", EXIT}, {"exit", EXIT},
30+
{"END", END}, {"end", END},
31+
{"PAUSE", PAUSE}, {"pause", PAUSE},
32+
{"RESUME", RESUME}, {"resume", RESUME},
33+
{"TITLE", TITLE}, {"title", TITLE}, // Remainder of line is a title.
34+
{"SETRUN", SETRUN}, {"setrun", SETRUN} // Remainder of line is a + integer run number.
35+
};
36+
37+
/* trim leading/trailing whitespace from a string: */
38+
static std::string
39+
trim(std::string& str) {
40+
std::string result(str);
41+
42+
// Trim the front:
43+
44+
result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](unsigned char ch) {
45+
return !std::isspace(ch);
46+
}));
47+
48+
// Trim from end:
49+
50+
result.erase(std::find_if(result.rbegin(), result.rend(), [](unsigned char ch) {
51+
return !std::isspace(ch);
52+
}).base(), result.end());
53+
54+
return result;
55+
}
56+
/* True if the parameter is a string that is only whitespace */
57+
bool
58+
isBlank(const std::string& str) {
59+
return std::all_of(str.begin(), str.end(), isspace);
60+
}
61+
/*
62+
* parseCommand implementation.
63+
*/
64+
ParsedCommand
65+
parseCommand(const std::string& line) {
66+
ParsedCommand parsed;
67+
68+
// Get the command word and map it to a command value:
69+
70+
std::stringstream words(line);
71+
std::string commandword;
72+
std::string remainder;
73+
words >> commandword;
74+
if (commandMap.find(commandword) == commandMap.end()) {
75+
parsed.s_error ="Invalid command keyword";
76+
// invalid (empty goes here too):
77+
return parsed; // Initializes to invalid
78+
}
79+
parsed.s_command = commandMap[commandword];
80+
std::getline(words, remainder);
81+
switch (parsed.s_command) {
82+
case BEGIN:
83+
case END: // These should have at most a whitespace remainder:
84+
case PAUSE:
85+
case RESUME:
86+
case EXIT:
87+
if (!isBlank(remainder)) {
88+
parsed.s_command = INVALID;
89+
parsed.s_error = "Unexpected characters following the command keyword";
90+
}
91+
break;
92+
case TITLE: // Remainder is the arg.
93+
parsed.s_stringarg = remainder;
94+
if (isBlank(remainder)) {
95+
parsed.s_command = INVALID;
96+
parsed.s_error = "TITLE needs a title string tail";
97+
} else {
98+
parsed.s_stringarg = trim(remainder);
99+
}
100+
break;
101+
case SETRUN: { // Remainder is a postitive integer.
102+
103+
std::stringstream tail(remainder);
104+
remainder = ""; // Since getline and end won't fill this.
105+
tail >> parsed.s_intarg;
106+
if (tail.bad() || tail.fail()) {
107+
parsed.s_command = INVALID;
108+
parsed.s_error = "SETRUN needs a run number";
109+
} else {
110+
// Run must be > 0:
111+
112+
if (parsed.s_intarg <= 0) {
113+
parsed.s_command = INVALID;
114+
parsed.s_error = "SETRUN's run number must be > 0";
115+
} else {
116+
// The remainder now must be empty:
117+
118+
getline(tail, remainder);
119+
120+
if (!isBlank(remainder)) {
121+
parsed.s_command = INVALID;
122+
parsed.s_error = "Unexpected characters following the command keyword";
123+
}
124+
}
125+
}
126+
}
127+
break;
128+
default: // Error to land here:
129+
parsed.s_command = INVALID;
130+
parsed.s_error = "The command parser has an error and took a case it should not have";
131+
}
132+
return parsed;
133+
}

0 commit comments

Comments
 (0)