diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32da7640..a6f6e082 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,26 +86,52 @@ jobs: if: matrix.configurations.compiler == 'gcc13' && matrix.cmake-build-type == 'Debug' uses: SonarSource/sonarcloud-github-c-cpp@v2 + - name: "Install timing system dependencies: etherbone" + if: matrix.configurations.compiler != 'emscripten' + shell: bash + run: | + git clone --branch v2.1.3 --depth=1 https://ohwr.org/project/etherbone-core.git + cd etherbone-core/api + touch ChangeLog # add an empty changelog file which is required by autotools + sed -e "s%AC_MSG_ERROR%AC_MSG_NOTICE%g" -i configure.ac + autoreconf -i + ./configure + make -j + sudo make install + + - name: "Install timing system dependencies: saftlib" + if: matrix.configurations.compiler != 'emscripten' + shell: bash + run: | + sudo apt-get -y install libsigc++-2.0-dev libxslt1-dev libboost-all-dev + git clone --branch v3.0.3 --depth=1 https://github.com/GSI-CS-CO/saftlib.git + cd saftlib + ./autogen.sh + ./configure + make + sudo make install + - name: Install picoscope libraries + if: matrix.configurations.compiler != 'emscripten' run: | # https://www.picotech.com/downloads/linux wget -O - https://labs.picotech.com/Release.gpg.key|sudo apt-key add - sudo add-apt-repository 'deb https://labs.picotech.com/rc/picoscope7/debian/ picoscope main' sudo apt update - sudo apt install -y udev libusb-1.0-0-dev libps3000a libps4000a libps5000a libps6000 libps6000a || true # ignore udev errors in post install because of udev in container + sudo apt install -y udev libusb-1.0-0-dev libps3000a libps4000a libps5000a libps6000 libps6000a libx11-dev libgl1-mesa-dev libsdl2-dev || true # ignore udev errors in post install because of udev in container - name: Configure CMake if: matrix.configurations.compiler != 'emscripten' shell: bash run: | - cmake -S . -B ../build -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DENABLE_COVERAGE=${{ matrix.configurations.compiler == 'gcc13' && matrix.cmake-build-type == 'Debug'}} + cmake -S . -B ../build -DENABLE_PICOSCOPE=TRUE -DENABLE_TIMING=TRUE -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DENABLE_COVERAGE=${{ matrix.configurations.compiler == 'gcc13' && matrix.cmake-build-type == 'Debug'}} - name: Configure CMake Emscripten if: matrix.configurations.compiler == 'emscripten' shell: bash run: | source ~/emsdk/emsdk_env.sh - emcmake cmake -S . -B ../build -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DENABLE_COVERAGE=OFF + emcmake cmake -S . -B ../build -DENABLE_PICOSCOPE=FALSE -DENABLE_TIMING=FALSE -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DENABLE_COVERAGE=OFF - name: Build if: matrix.configurations.compiler != 'gcc13' || matrix.cmake-build-type != 'Debug' diff --git a/.gitignore b/.gitignore index b3f8c10c..76bb108d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ language.settings.xml # python venv files virtualenv/ + +# cmake build directories +cmake-build-*/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 76e9ae62..4ed15aa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 23) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +include(cmake/CMakeRC.cmake) include(FetchContent) set(ENABLE_TESTING OFF) @@ -20,7 +21,57 @@ FetchContent_Declare( GIT_TAG bf8388f61103571dee3061a4ef23292a320d9dbf # head as of 2023-08-21 ) -FetchContent_MakeAvailable(graph-prototype ut) +if (EMSCRIPTEN) + FetchContent_MakeAvailable(graph-prototype ut) +else () + FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG v1.90 + ) + + # Enables 32 bit vertex indices for ImGui + add_compile_definitions("ImDrawIdx=unsigned int") + FetchContent_Declare( + implot + GIT_REPOSITORY https://github.com/epezent/implot.git + GIT_TAG v0.16 + ) + FetchContent_MakeAvailable(imgui implot graph-prototype ut) + + find_package(SDL2 REQUIRED) + find_package(OpenGL REQUIRED COMPONENTS OpenGL) + + # imgui and implot are not CMake Projects, so we have to define their targets manually here + add_library(imgui + OBJECT + ${imgui_SOURCE_DIR}/imgui_demo.cpp + ${imgui_SOURCE_DIR}/imgui_draw.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp + ${imgui_SOURCE_DIR}/imgui_tables.cpp + ${imgui_SOURCE_DIR}/imgui_widgets.cpp + ${imgui_SOURCE_DIR}/imgui.cpp + ${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp + ) + target_link_libraries(imgui PUBLIC SDL2::SDL2 OpenGL::GL) + target_compile_options(imgui PRIVATE -w) # imgui does lots of oldstyle casts and pointer arithmethic leading to many warnings + target_include_directories(imgui BEFORE + PUBLIC SYSTEM + ${imgui_SOURCE_DIR} + ${imgui_SOURCE_DIR}/backends + ) + + add_library(implot + OBJECT ${implot_SOURCE_DIR}/implot_demo.cpp ${implot_SOURCE_DIR}/implot_items.cpp ${implot_SOURCE_DIR}/implot.cpp + ) + target_compile_options(implot PRIVATE -w) # implot does lots of oldstyle casts and pointer arithmethic leading to many warnings + target_include_directories(implot BEFORE + PUBLIC SYSTEM + ${implot_SOURCE_DIR} + ) + target_link_libraries(implot PUBLIC imgui) +endif () add_library(gr-digitizers-options INTERFACE) include(cmake/CompilerWarnings.cmake) @@ -33,15 +84,22 @@ endif() if(NOT EMSCRIPTEN) option(ENABLE_PICOSCOPE "Enable PicoScope support" ON) + option(ENABLE_TIMING "Enable TimingReceiver support" ON) set(PICOSCOPE_PREFIX "/opt/picoscope" CACHE PATH "Picoscope drivers prefix") # TODO use proper find_package endif() +find_package(PkgConfig REQUIRED) + if(ENABLE_PICOSCOPE) - find_package(PkgConfig REQUIRED) pkg_check_modules(zlib REQUIRED IMPORTED_TARGET zlib) pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) endif() +if(ENABLE_TIMING AND NOT EMSCRIPTEN) + pkg_check_modules(saftlib REQUIRED IMPORTED_TARGET saftlib) + pkg_check_modules(etherbone REQUIRED IMPORTED_TARGET etherbone) +endif() + option(ENABLE_GR_DIGITIZERS_TESTING "Enable gr-digitizers Test Builds" ON) if (ENABLE_GR_DIGITIZERS_TESTING AND UNIX AND NOT APPLE) list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") @@ -66,4 +124,3 @@ if (ENABLE_GR_DIGITIZERS_TESTING AND UNIX AND NOT APPLE) endif () add_subdirectory(blocklib) - diff --git a/README.md b/README.md index 4ef1c5da..b352268e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # gr-digitizers `gr-digitizers` is a collection of gnuradio blocks for signal processing. @@ -15,6 +14,7 @@ In a nutshell, we have three main dependencies: - [ROOT](https://root.cern/) - [PicoScope Drivers](https://www.picotech.com/downloads/linux) (optional) - [LimeSuite](https://github.com/myriadrf/LimeSuite) (optional) + - [etherbone](https://ohwr.org/project/etherbone-core/tree/master/api) and [saftlib](https://github.com/GSI-CS-CO/saftlib) (optional for the timing receiver blocks) In the following it is assumed that ROOT is installed to `/opt/root`, and the picoscope drivers to `/opt/picoscope` (which is e.g. done when using the Ubuntu packages). @@ -35,15 +35,18 @@ ninja sudo ninja install ``` +### Build etherbone and saftlib +To build and test the timing related dependencies, see the [readme for the timing block](blocklib/timing/README.md) + ### Build gr-digitizers To build gr-digitizers, run: ```shell -$ meson setup builddir . -Dlibpicoscope_prefix=/opt/picoscope -Dlibroot_prefix=/opt/root -Dlimesuite_prefix=/opt/limesuite -$ cd builddir -$ ninja -$ LD_LIBRARY_PATH=/opt/root/lib ninja test # run unit tests, optional +$ cmake -S . -B build -Dlibpicoscope_prefix=/opt/picoscope -Dlibroot_prefix=/opt/root -Dlimesuite_prefix=/opt/limesuite +$ cmake --build build -j +$ cd build +$ LD_LIBRARY_PATH=/opt/root/lib ctest --output-on-failure # run unit tests, optional $ ninja install ``` @@ -55,7 +58,7 @@ By default, only unit tests without any hardware dependencies are executed. In o related tests, execute the below command: ```shell -$ LD_LIBRARY_PATH=/opt/root/lib PICOSCOPE_RUN_TESTS=3000a ninja test +$ LD_LIBRARY_PATH=/opt/root/lib PICOSCOPE_RUN_TESTS=3000a ctest --output-on-failure # run unit tests, optional ``` Running multiple hardware tests together, like `PICOSCOPE_RUN_TESTS=3000a,4000a`, is technically possible diff --git a/blocklib/CMakeLists.txt b/blocklib/CMakeLists.txt index af94ebcd..5191b476 100644 --- a/blocklib/CMakeLists.txt +++ b/blocklib/CMakeLists.txt @@ -4,3 +4,7 @@ add_subdirectory(digitizers) if (ENABLE_PICOSCOPE) add_subdirectory(picoscope) endif () + +if (ENABLE_TIMING) + add_subdirectory(timing) +endif () diff --git a/blocklib/picoscope/CMakeLists.txt b/blocklib/picoscope/CMakeLists.txt index 207201b4..1700fd62 100644 --- a/blocklib/picoscope/CMakeLists.txt +++ b/blocklib/picoscope/CMakeLists.txt @@ -1,5 +1,5 @@ # TODO do not hardcode -add_library(ps4000a SHARED IMPORTED) +add_library(ps4000a SHARED IMPORTED GLOBAL) set_property(TARGET ps4000a PROPERTY IMPORTED_LOCATION ${PICOSCOPE_PREFIX}/lib/libps4000a.so) target_link_libraries(ps4000a INTERFACE PkgConfig::zlib PkgConfig::libusb) diff --git a/blocklib/timing/CMakeLists.txt b/blocklib/timing/CMakeLists.txt new file mode 100644 index 00000000..eb36d4b3 --- /dev/null +++ b/blocklib/timing/CMakeLists.txt @@ -0,0 +1,19 @@ +if (NOT EMSCRIPTEN) + cmrc_add_resource_library( + ui_assets + NAMESPACE + ui_assets + WHENCE + ${imgui_SOURCE_DIR}/misc/fonts + ${imgui_SOURCE_DIR}/misc/fonts/Roboto-Medium.ttf) + + add_library(timing INTERFACE) + target_include_directories(timing INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/) + + add_executable(test-timing src/test-timing.cpp) + target_link_libraries(test-timing PRIVATE gr-digitizers-options timing PkgConfig::saftlib PkgConfig::etherbone imgui implot gnuradio-algorithm gnuradio-core ui_assets) +endif () + +if (ENABLE_GR_DIGITIZERS_TESTING) + add_subdirectory(test) +endif () diff --git a/blocklib/timing/README.md b/blocklib/timing/README.md new file mode 100644 index 00000000..cee79d2c --- /dev/null +++ b/blocklib/timing/README.md @@ -0,0 +1,73 @@ +# White-Rabbit based timing blocks and test utilities + +This directory contains the timing realated gnuradio blocks along with some testing utilites and setup instructions. + +# Dependencies +For actually using the blocks and utilities built by this you will also need actual timing receiver hardware, the +[wishbone kernel module](https://ohwr.org/project/fpga-config-space/tree/realtime_fixes/pcie-wb) has to be loaded and the saftlibd daemon has to be running and configured properly. + +```shell +git clone --branch v2.1.3 --depth=1 https://ohwr.org/project/etherbone-core.git +cd etherbone-core/api +./autogen.sh +./configure +make -j +sudo make install +``` + +```shell +git clone --branch v3.0.3 --depth=1 https://github.com/GSI-CS-CO/saftlib.git +cd saftlib +./autogen.sh +./configure +make -j +sudo make install +``` + +# Setting up usage with an actual timing card + +- build etherbone and saftlib as described above +- build gr-digitizers with `-DENABLE_TIMING=True` +- load the required kernel modules if the card is connected via PCIe: `modprobe pcie_wb` + - verify they are loaded correctly: `lsmod | grep wb`, `dmesg | tail` +- start saftd demon and attach timing card: `saftd tr0:dev/wbm0` (where `dev/wbm0` is the device path of the wishbone device without leading slash) +- if there is no physical timing signal plugged into the card, the card has to be switched to master mode: + - `eb-console /dev/wbm0` and enter `mode master` and hit return. The console should print a message that the timing mode is changed. + - eb-console is included in [bel_projects](https://github.com/GSI-CS-CO/bel_projects). + If you only need the single tool and have installed etherbone on your system by other means you can build only eb-console: + ```bash + $ git clone https://github.com/GSI-CS-CO/bel_projects + $ cd bel_projects/tools + $ make EB=/usr/include eb-console # or point the EB variable to whatever prefix you installed etherbone to + $ ./eb-console dev/wbm0 # or dev/ttyUSB0 + ``` +- verify that everything works with saft-ctl: + - ``` bash + $ saft-ctl tr0 -ijkst + saftlib source version : saftlib 3.0.3 (6aab401-dirty): Aug 29 2023 09:50:19 + saftlib build info : built by unknown on Jan 1 1980 00:00:00 with localhost running + devices attached on this host : 1 + device: /de/gsi/saftlib/tr0, name: tr0, path: dev/wbm0, gatewareVersion : 6.1.2 + --gateware version info: + ---- Mon Aug 09 08:48:31 CEST 2021 + ---- fallout-v6.1.2 + ---- Arria V (5agxma3d4f27i3) + ---- CentOS Linux release 7.9.2009 (Core), kernel 3.10.0-1160.36.2.el7.x86_64 + ---- pexaria5 +db[12] +wrex1 + ---- Timing Group Jenkins + ---- tsl021.acc.gsi.de + ---- pci_control + ---- Version 18.1.0 Build 625 09/12/2018 SJ Standard Edition + ---- fallout-3847 + 6.1.2 + current temperature (Celsius): 48 + WR locked, time: 0x001181787bda0268 + receiver free conditions: 255, max (capacity of HW): 0(256), early threshold: 4294967296 ns, latency: 4096 ns + sinks instantiated on this host: 1 + /de/gsi/saftlib/tr0/software/_77 (minOffset: -1000000000 ns, maxOffset: 1000000000 ns) + -- actions: 0, delayed: 0, conflict: 0, late: 0, early: 0, overflow: 0 (max signalRate: 10Hz) + -- conditions: 0 + ``` + - snoop on all events: `saft-ctl tr0 snoop 0x0 0x0 0x0` and on another terminal inject an event: `saft-ctl tr0 inject 0x1154000140000000 0x15 1000000` to verify it arrives. + - finally launch `build/blocklib/timing/test-timing` to show the UI + diff --git a/blocklib/timing/include/timing.hpp b/blocklib/timing/include/timing.hpp new file mode 100644 index 00000000..0897d601 --- /dev/null +++ b/blocklib/timing/include/timing.hpp @@ -0,0 +1,406 @@ +#ifndef GR_DIGITIZERS_TIMING_HPP +#define GR_DIGITIZERS_TIMING_HPP +#include +// gr +#include +// timing +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// gr +#include + +using saftlib::SAFTd_Proxy; +using saftlib::TimingReceiver_Proxy; +using saftlib::SoftwareActionSink_Proxy; +using saftlib::SoftwareCondition_Proxy; + +static std::chrono::time_point taiNsToUtc(uint64_t input) { + return std::chrono::utc_clock::to_sys(std::chrono::tai_clock::to_utc(std::chrono::tai_clock::time_point{} + std::chrono::nanoseconds(input))); +} + +class Timing { +public: + static const int milliToNano = 1000000; + static const int minTriggerOffset = 100; + /** + * Structure to atuomatically encode and decode GSI/FAIR specific timing events as documented in: + * https://www-acc.gsi.de/wiki/Timing/TimingSystemEvent + * + * The Timing system transmits 256 bit of metadata per timing event consisting of the following high level fields: + * bits |size| name | description + * --------|----|------------------------|------------------------------------------------------------------- + * 000-063 | 64 | EventID | contains application specific data, Timing Hardware performs a prefix match on this data + * 064-127 | 64 | Param | application specific data + * 128-159 | 32 | Reserved | unused/not covered here + * 160-191 | 32 | Timing Extension Field | used internally/not covered here + * 192-255 | 64 | Timestamp | time in nanoseconds since the origin of the TAI epoch + * + * At GSI/FAIR the EventID and Param fields are split into different fields: + * + * EventID: + * bitsBE |bitsLE |size| name | description + * ------|-------|----|--------------|------------------------------------------------------------------- + * 00-03 | 60-63 | 4 | FID | Format ID: always equals 0x1 + * 04-15 | 48-59 | 12 | GID | Timing group ID: see event_definitions.hpp for the existing timing groups + * 16-27 | 36-47 | 12 | EVENTNO | Event Number: determines the type of event e.g CME_BP_START, see event_definitions.hpp + * 28-31 | 32-35 | 4 | FLAGS | + * 28 | 34 | 1 | BEAM-IN | + * 29 | 34 | 1 | BPC-START | First event in a new Beam Production Chain + * 30 | 33 | 1 | RESERVED 1 | + * 31 | 32 | 1 | RESERVED 2 | + * 32-43 | 20-31 | 12 | SID | Sequence ID + * 44-57 | 6-19 | 14 | BPID | Beam Process ID + * 58-63 | 0- 5 | 6 | reserved | + * 58 | 5 | 1 | reserved | + * 59 | 4 | 1 | ReqNoBeam | + * 40-63 | 0- 3 | 4 | VirtAcc | Virtual Accelerator + * + * BigEndian 0 4 16 28 32 44 58 + * LittleEndian 60 48 36 20 20 6 0 + * 0x f f f f f f f f f f f f f f 8 8 f + * 0b 1111 111111111111 111111111111 1111 111111111111 11111111111111 111111 + * size [bits] 4 12 12 4 12 14 6 + * FID GID EVENTNO FLAGS SID BPID reserved + * + * param: + * bitsBE|bitsLE |size| name | description + * ------|-------|----|--------------|------------------------------------------------------------------- + * 0-21 | 42-63 | 22 | BPCID | Beam Production Chain ID + * 22-63 | 0-41 | 42 | BPCTS | Beam Production Chain Timestamp + * + * BigEndian 0 22 63 + * LittleEndian 63 44 0 + * 0x fffff8 8ffffffffff + * 0b 1111111111111111111111 111111111111111111111111111111111111111111 + * size [bits] 22 42 + * BPCID BPCTS + * + * The (BPCID, SID, BPID, GID) tuple corresponds to the timing selector (`FAIR.SELECTOR.C=__:S=__;P=__:T=__`) + * described in https://github.com/fair-acc/opencmw-cpp/blob/main/assets/F-CS-SIS-en-B_0006_FAIR_Service_Middle_Ware_V1_0.pdf + * + * The additional fields `executed` and `flags` do not correspond to fields in the timing message but are used to + * store local meta-information on whether the event was processed in time. + */ + struct Event { + // eventid - 64 + uint8_t fid = 1; // 4 + uint16_t gid; // 12 + uint16_t eventNo; // 12 + bool flagBeamin; + bool flagBpcStart; + bool flagReserved1; + bool flagReserved2; + uint16_t sid; //12 + uint16_t bpid; //14 + bool reserved; + bool reqNoBeam; + uint8_t virtAcc; // 4 + // param - 64 + uint32_t bpcid; // 22 + uint64_t bpcts; // 42 + // timing system + uint64_t time = 0; + uint64_t executed = 0; + uint16_t flags = 0x0; + + Event(const Event&) = default; + Event(Event&&) = default; + Event& operator=(const Event&) = default; + Event& operator=(Event&&) = default; + + template + static constexpr ReturnType extractField(uint64_t value) { + static_assert(position + bitsize <= 64); // assert that we only consider existing bits + static_assert(std::numeric_limits::max() >= ((1UL << bitsize) - 1)); // make sure the data fits into the return type + return static_cast((value >> position) & ((1UL << bitsize) - 1)); + }; + + template + static constexpr uint64_t fromField(FieldType value) { + static_assert(position + bitsize <= 64); + static_assert(std::numeric_limits::max() >= ((1UL << bitsize) - 1)); + return ((value & ((1UL << bitsize) - 1)) << position); + }; + + explicit Event(uint64_t timestamp = 0, uint64_t id = 1UL << 60, uint64_t param= 0, uint16_t _flags = 0, uint64_t _executed = 0) : + // id + fid { extractField(id) }, + gid { extractField(id) }, + eventNo { extractField(id) }, + flagBeamin { extractField(id) }, + flagBpcStart { extractField(id) }, + flagReserved1 { extractField(id) }, + flagReserved2 { extractField(id) }, + sid { extractField(id) }, + bpid { extractField(id) }, + reserved { extractField(id) }, + reqNoBeam { extractField(id) }, + virtAcc { extractField(id) }, + // param + bpcid { extractField(param) }, + bpcts { extractField(param) }, + time { timestamp}, + executed { _executed}, + flags { _flags} { } + + [[nodiscard]] uint64_t id() const { + // clang-format:off + return fromField< 0, 4>(virtAcc) + + fromField< 4, 1>(reqNoBeam) + + fromField< 5, 1>(reserved) + + fromField< 6, 14>(bpid) + + fromField<20, 12>(sid) + + fromField<32, 1>(flagReserved2) + + fromField<33, 1>(flagReserved1) + + fromField<34, 1>(flagBpcStart) + + fromField<35, 1>(flagBeamin) + + fromField<36, 12>(eventNo) + + fromField<48, 12>(gid) + + fromField<60, 4>(fid); + // clang-format:on + } + + [[nodiscard]] uint64_t param() const { + return fromField< 0, 42>(bpcts) + fromField< 42, 22>(bpcid); + } + + static std::optional fromString(std::string_view line) { + using std::operator""sv; +#if defined(__clang__) + std::array elements{}; + std::size_t found = 0; + std::size_t startingIndex = 0; + for (std::size_t i = 0; i <= line.size() && found < elements.size(); i++) { + if (i == line.size() || line[i] == ' ') { + if (startingIndex < i) { + auto parse = [](auto el) { return std::stoul(std::string(std::string_view(el)), nullptr, 0); }; + elements[found++] = parse(line.substr(startingIndex, i - startingIndex)); + } + startingIndex = i; + } + } + if (found >= 3) { + return Event{elements[2], elements[0], elements[1]}; + } +#else + auto event = std::views::split(line, " "sv) | std::views::take(3) + | std::views::transform([](auto n) { return std::stoul(std::string(std::string_view(n)), nullptr, 0); }) + | std::views::adjacent_transform<3>([](auto a, auto b, auto c) {return Timing::Event{c, a, b};}); + if (!event.empty()) { + return event.front(); + } +#endif + return {}; + } + + static void loadEventsFromString(std::vector &events, std::string_view string) { + events.clear(); + using std::operator""sv; + try { + for (const auto line : std::views::split(string, "\n"sv)) { + auto event = Timing::Event::fromString(std::string_view{line}); + if (event) { + events.push_back(*event); + } + } + } catch (std::invalid_argument &e) { + events.clear(); + fmt::print("Error parsing data, cannot convert string to number: {}\n### data ###\n{}\n### data end ###\n", e.what(), string); + } catch (std::out_of_range &e) { + events.clear(); + fmt::print("Error parsing data, value out of range: {}\n### data ###\n{}\n### data end ###\n", e.what(), string); + } + } + + }; + + struct Trigger { + std::array outputs; + uint64_t id; + double delay; // [ms] + double flattop; // [ms] + + bool operator<=>(const Trigger&) const = default; + }; + + gr::CircularBuffer snooped{10000}; + std::vector> outputs; + std::map triggers; + std::vector events = {}; +private: + decltype(snooped.new_writer()) snoop_writer = snooped.new_writer(); + bool tried = false; + std::shared_ptr saftd; + std::shared_ptr sink; + std::shared_ptr condition; +public: + bool initialized = false; + bool simulate = false; + uint64_t snoopID = 0x0; + uint64_t snoopMask = 0x0; + std::shared_ptr receiver; + +private: + void updateExistingTrigger(const Trigger &trigger, const std::map::iterator &existing, const std::string& output) const { + auto proxy = saftlib::Output_Proxy::create(output); + if (trigger.delay != existing->second.delay || trigger.flattop != existing->second.flattop) { // update condition for rising edge + auto matchingConditions = proxy->getAllConditions() + | std::views::transform([](const auto &cond) { return saftlib::OutputCondition_Proxy::create(cond); }) + | std::views::filter([&trigger](const auto &cond) { return cond->getID() == trigger.id && cond->getMask() == std::numeric_limits::max(); }); + std::ranges::for_each(matchingConditions, [&trigger](const auto &cond) { + if (cond->getOn()) { + cond->setOffset(static_cast(trigger.delay) * milliToNano + minTriggerOffset); + } else { + cond->setOffset(static_cast(trigger.delay + trigger.flattop) * milliToNano + minTriggerOffset); + } + }); + } + } + + void removeHardwareTrigger(const Trigger &trigger, const std::string &output) const { + auto proxy = saftlib::Output_Proxy::create(output); + auto matchingConditions = proxy->getAllConditions() + | std::views::transform([](const auto &cond) { return saftlib::OutputCondition_Proxy::create(cond); }) + | std::views::filter([&trigger](const auto &cond) { return cond->getID() == trigger.id && cond->getMask() == std::numeric_limits::max(); }); + std::ranges::for_each(matchingConditions, [](const auto &cond) { + cond->Destroy(); + }); + } + + void newHardwareTrigger(const Trigger &trigger, const std::string &output) const { + auto proxy = saftlib::Output_Proxy::create(output); + proxy->NewCondition(true, trigger.id, std::numeric_limits::max(), static_cast(trigger.delay) * milliToNano + minTriggerOffset, true); + proxy->NewCondition(true, trigger.id, std::numeric_limits::max(), static_cast(trigger.delay + trigger.flattop) * milliToNano + minTriggerOffset, false); + } + +public: + void updateSnoopFilter() { + if (simulate) return; + if (condition) { + condition->Destroy(); + } + condition = SoftwareCondition_Proxy::create(sink->NewCondition(false, snoopID, snoopMask, 0)); + condition->setAcceptLate(true); + condition->setAcceptEarly(true); + condition->setAcceptConflict(true); + condition->setAcceptDelayed(true); + condition->SigAction.connect( + [this](uint64_t id, uint64_t param, const saftlib::Time& deadline, const saftlib::Time& executed, + uint16_t flags) { + this->snoop_writer.publish( + [id, param, &deadline, &executed, flags](std::span buffer) { + buffer[0] = Timing::Event{deadline.getTAI(), id, param, flags, executed.getTAI()}; + }, 1); + }); + condition->setActive(true); + } + + void initialize() { + if (simulate) { + initialized = true; + } else { + try { + saftd = SAFTd_Proxy::create(); + // get a specific device + std::map devices = saftd->getDevices(); + if (devices.empty()) { + std::cerr << "" << std::endl; + fmt::print("No devices attached to saftd, continuing with simulated timing\n"); + simulate = true; + initialized = true; + return; + } + receiver = TimingReceiver_Proxy::create(devices.begin()->second); + sink = SoftwareActionSink_Proxy::create(receiver->NewSoftwareActionSink("gr_timing_example")); + updateSnoopFilter(); + for (const auto & [i, output]: receiver->getOutputs() | std::views::enumerate ) { + const auto &[name, port] = output; + outputs.emplace_back(i, name, port); + } + initialized = true; + } catch (saftbus::Error &e){ + fmt::print("Error initializing saftbus client: {}\ncontinuing with simulated timing\n", e.what()); + simulate = true; + initialized = true; + return; + } + } + } + + void process() { + if (!initialized && !tried) { + tried = true; + initialize(); + } else if (initialized && !simulate) { + const auto startTime = std::chrono::system_clock::now(); + while(true) { + auto duration = std::chrono::duration_cast(startTime - std::chrono::system_clock::now() + std::chrono::milliseconds(5)).count(); + if (duration > 0) { + saftlib::wait_for_signal(static_cast(std::clamp(duration, 50L, std::numeric_limits::max()+0L))); + } else { + break; + } + } + } + } + + void injectEvent(const Event &ev, uint64_t time_offset) { + if (simulate && ((ev.id() & snoopMask) == (snoopID & snoopMask)) ) { + this->snoop_writer.publish( + [ev, time_offset](std::span buffer) { + buffer[0] = ev; + buffer[0].time += time_offset; + buffer[0].executed = buffer[0].time; + }, 1); + } else if (!simulate) { + receiver->InjectEvent(ev.id(), ev.param(), saftlib::makeTimeTAI(ev.time + time_offset)); + } + } + + unsigned long currentTimeTAI() { + if (simulate) { + return static_cast(std::max(0L, duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count())); + } + return receiver->CurrentTime().getTAI(); + } + + void updateTrigger(Trigger &trigger) { + auto existing = triggers.find(trigger.id); + if (existing != triggers.end() && existing->second == trigger) { + return; // nothing changed + } + if (!simulate) { +#if defined(__clang__) + std::size_t i = 0; + for (const auto &output : outputs) { + bool enabled = trigger.outputs[i++]; +#else + for (const auto [i, output, enabled] : std::views::zip(std::views::iota(0), outputs, trigger.outputs)) { +#endif + if (enabled && (existing == triggers.end() || !existing->second.outputs[static_cast(i)])) { // newly enabled + newHardwareTrigger(trigger, std::get<2>(output)); + auto [inserted, _] = triggers.try_emplace(trigger.id, trigger); + existing = inserted; + } else if (!enabled && existing != triggers.end() && existing->second.outputs[static_cast(i)]) { // newly disabled + removeHardwareTrigger(trigger, std::get<2>(output)); + } else if (existing != triggers.end()) { + updateExistingTrigger(trigger, existing, std::get<2>(output)); + } + } + } + triggers.insert_or_assign(trigger.id, trigger); + } +}; + +#endif diff --git a/blocklib/timing/src/ImScoped.hpp b/blocklib/timing/src/ImScoped.hpp new file mode 100644 index 00000000..0eedec9d --- /dev/null +++ b/blocklib/timing/src/ImScoped.hpp @@ -0,0 +1,57 @@ +#include +#include +#include +/** + * A small wrapper to automatically wrap ImGui Begin/End pairs to prevent mismatched calls. + * Taken from: https://github.com/ocornut/imgui/issues/2096#issuecomment-1463837461 + * Limitations: + * - no implot support yet + * - BeginChild needs a more manual wrapper because of varargs + * - small issue: this wrapper breaks default arguments, they have to be always specified + */ +namespace ImScoped { +namespace detail { +template +class Widget { + bool shown = false; +public: + template + explicit Widget (A&&... a) + requires (!std::same_as> && ...) + : shown{ + [] (AA&&... aa) { + return Begin(std::forward(aa)...); + } (std::forward(a)...) + } {} + ~Widget () { if (UnconditionalEnd || shown) End(); } + Widget(Widget&) = delete; + Widget(Widget&&) = delete; + Widget& operator=(const Widget&) = delete; + Widget& operator=(Widget&&) = delete; + explicit operator bool () const& { return shown; } + explicit operator bool () && = delete; // force to store a reference to the widget in the if condition to extend the lifetime +}; +template class WidgetVoid { +public: + template + requires (!std::same_as> && ...) + [[nodiscard]] explicit WidgetVoid (A&&... a) { + Begin(std::forward(a)...); + } + ~WidgetVoid () { End(); } + WidgetVoid(WidgetVoid&) = delete; + WidgetVoid(WidgetVoid&&) = delete; + WidgetVoid& operator=(const WidgetVoid&) = delete; + WidgetVoid& operator=(WidgetVoid&&) = delete; +}; +} // namespace detail + +using Window = detail::Widget<&ImGui::Begin, &ImGui::End, true>; +using TabBar = detail::Widget<&ImGui::BeginTabBar, &ImGui::EndTabBar>; +using TabItem = detail::Widget<&ImGui::BeginTabItem, &ImGui::EndTabItem>; +using Table = detail::Widget<&ImGui::BeginTable, &ImGui::EndTable>; +using Menu = detail::Widget<&ImGui::BeginMenu, &ImGui::EndMenu>; +using ListBox= detail::Widget<&ImGui::BeginListBox, &ImGui::EndListBox>; +using Disabled = detail::WidgetVoid<&ImGui::BeginDisabled, &ImGui::EndDisabled>; +using Tooltip = detail::WidgetVoid<&ImGui::BeginTooltip, &ImGui::EndTooltip>; +} // namespace ImScoped diff --git a/blocklib/timing/src/event_definitions.hpp b/blocklib/timing/src/event_definitions.hpp new file mode 100644 index 00000000..30ba7bdf --- /dev/null +++ b/blocklib/timing/src/event_definitions.hpp @@ -0,0 +1,443 @@ +#ifndef GR_DIGITIZERS_EVENT_DEFINITIONS_HPP +#define GR_DIGITIZERS_EVENT_DEFINITIONS_HPP + +static const std::array bpcidColors{ + // colors taken from: https://git.acc.gsi.de/fcc-applications/common-context-widget-reactor/src/branch/master/common-context-widget-fx/src/main/resources/de/gsi/fcc/applications/common/contextwidget/fx/StylesColorsBase.css + ImColor{0x00, 0x70, 0xC0}, // 0x0070C0 - materials + ImColor{0x00, 0xB0, 0x50}, // 0x00B050 - bio + ImColor{0x5C, 0xB7, 0xC3}, // 0x5CB7C3 - cbm + ImColor{0x70, 0x30, 0xA0}, // 0x7030A0 - nustar-basic + ImColor{0xC6, 0x59, 0x11}, // 0xC65911 - machine + ImColor{0xFF, 0xDA, 0x4A}, // 0xFFDA4A - nustar-r3b + ImColor{0xC4, 0x8B, 0xD0}, // 0xC48BD0 - nustar-she-c + ImColor{0xE1, 0x4C, 0xFF}, // 0xE14CFF - nustar-she-p + ImColor{0xFF, 0xFF, 0x00}, // 0xFFFF00 - nustar-hades + ImColor{0xFF, 0xA6, 0x32}, // 0xFFA632 - plasma + ImColor{0x00, 0xFC, 0xD6}, // 0x00FCD6 - appa-pp + ImColor{0x80, 0x80, 0x80}, // 0x808080 - undefined + ImColor{0x9e, 0x9e, 0x9e} // 0x9e9e9e - invalid +}; + +// generic colormap for timing ids +static const std::array colorlist { // gemerated with http://medialab.github.io/iwanthue/ + ImColor{78,172,215,120}, ImColor{200,76,41,120}, ImColor{85,199,102,120}, ImColor{186,84,191,120}, + ImColor{101,173,51,120}, ImColor{117,98,204,120}, ImColor{169,180,56,120}, ImColor{211,76,146,120}, + ImColor{71,136,57,120}, ImColor{209,69,88,120}, ImColor{86,192,158,120}, ImColor{217,132,45,120}, + ImColor{102,125,198,120}, ImColor{201,160,65,120}, ImColor{199,135,198,120}, ImColor{156,176,104,120}, + ImColor{168,85,112,120}, ImColor{55,132,95,120}, ImColor{203,126,93,120}, ImColor{118,109,41,120}, +}; + +// https://www-acc.gsi.de/wiki/Timing/TimingSystemGroupsAndMachines#Groups_for_Operation +static const std::map> timingGroupTable{ + {200, {"YRT1_TO_YRT1LQ1 ", "from ion source 1 to merging quadrupole"}}, + {201, {"YRT1LQ1_TO_YRT1LC1 ", "from merging quadrupole at ion sources to chopper"}}, + {202, {"YRT1LC1_TO_YRT1MH2 ", "injector from chopper to merging dipole"}}, + {203, {"YRT1MH2_TO_CRYRING ", "from merging dipole to ring"}}, + {210, {"CRYRING_RING ", "CRYRING ring"}}, + {211, {"CRYRING_COOLER ", "CRYRING electron cooler"}}, + {212, {"CRYRING_TO_YRE ", "Experiment line from CRYRING to YRE"}}, + {300, {"SIS18_RING ", "SIS18 ring"}}, + {301, {"SIS18_COOLER ", "SIS18 electron cooler"}}, + {310, {"SIS100_RING ", "SIS100 ring"}}, + {340, {"ESR_RING ", "ESR ring"}}, + {341, {"ESR_COOLER ", "ESR electron cooler"}}, + {345, {"ESR_TO_HTR ", "ESR to HITRAP"}}, + {350, {"CR_RING ", "CR ring"}}, + {360, {"HESR_RING ", "HESR ring"}}, + {400, {"TO_BE_DISCUSSED ", "PLINAC proton source"}}, + {448, {"PZU_QR ", "UNILAC Timing - Source Right"}}, + {449, {"PZU_QL ", "UNILAC Timing - Source Left"}}, + {450, {"PZU_QN ", "UNILAC Timing - Source High Charge State Injector (HLI)"}}, + {451, {"PZU_UN ", "UNILAC Timing - High Charge State Injector (HLI)"}}, + {452, {"PZU_UH ", "UNILAC Timing - High Current Injector (HSI)"}}, + {453, {"PZU_AT ", "UNILAC Timing - Alvarez Cavities"}}, + {454, {"PZU_TK ", "UNILAC Timing - Transfer Line"}}, + {464, {"GTK7BC1L_TO_PLTKMH2__GS ", "GTK7BC1L to PLTKMH2 - shared group controlled by SIS18 accelerator"}}, + {465, {"GTK7BC1L_TO_PLTKMH2__GU ", "GTK7BC1L to PLTKMH2 - shared group controlled by UNILAC accelerator"}}, + {466, {"PLTKMH2_TO_SIS18__GS ", "PLTKMH2 to SIS18 - shared group controlled by SIS18 accelerator"}}, + {467, {"PLTKMH2_TO_SIS18__GU ", "PLTKMH2 to SIS18 - shared group controlled by UNILAC accelerator"}}, + {500, {"SIS18_TO_GTS1MU1 ", "SIS extraction to GTS1MU1"}}, + {501, {"GTS1MU1_TO_GTE3MU1 ", "GTS1MU1 to GTE3MU1"}}, + {502, {"GTE3MU1_TO_GTS6MU1 ", "GTE3MU1 to GTS6MU1"}}, + {503, {"GTS6MU1_TO_GTS6MU1 ", "GTS6MU1_TO_GTS6MU1"}}, + {504, {"GTS6MU1_TO_ESR ", "GTS6MU1 to ESR"}}, + {505, {"GTS1MU1_TO_GTS3MU1 ", "GTS1MU1 to GTS3MU1"}}, + {506, {"GTS3MU1_TO_HHD ", "GTS3MU1 to HHD Dump"}}, + {507, {"GTS3MU1_TO_GHFSMU1 ", "GTS3MU1 to GHFSMU1"}}, + {508, {"GHFSMU1_TO_HFS ", "Experiment line to HFS"}}, + {509, {"GHFSMU1_TO_GTS6MU1 ", "GHFSMU1 to GTS6MU1"}}, + {510, {"GTS6MU1_TO_GTS7MU1 ", "GTS6MU1 to GTS7MU1"}}, + {511, {"GTE3MU1_TO_GHHTMU1 ", "GTE3MU1 to GHHTMU1"}}, + {512, {"GHHTMU1_TO_HHT ", "Experiment line to the high temperature experiment HHT"}}, + {513, {"GHHTMU1_TO_GTH3MU1 ", "GHHTMU1 to GTH3MU1"}}, + {514, {"GTH3MU1_TO_GTP1MU1 ", "GTH3MU1 to pion branch GTV2MU3"}}, + {515, {"GTP1MU1_TO_HADES ", "Experiment line to the electron-positron-detector HADES (High Acceptance Di-Electron Spectrometer)"}}, + {516, {"GTH3MU1_TO_GTS7MU1 ", "GTH3MU1 to GTS7MU1"}}, + {517, {"GTS7MU1_TO_GTH4MU1 ", "GTS7MU1 to GTH4MU1"}}, + {518, {"GTH4MU1_TO_HTM ", "Experiment line to the particle therapy experiment HTM"}}, + {519, {"GTH4MU1_TO_GTH4MU2 ", "GTH4MU1 to GTH4MU2"}}, + {520, {"GTP1MU1_TO_GTH4MU2 ", "GTP1MU1 to GTH4MU2 pion transfer line"}}, + {521, {"GTH4MU2_TO_GTV1MU1 ", "GTH4MU2 to GTV1MU1"}}, + {522, {"GTV1MU1_TO_GHTDMU1 ", "GTV1MU1 to GHTDMU1"}}, + {523, {"GTV1MU1_TO_GTV2MU2 ", "GTV1MU1 to GTV2MU2"}}, + {524, {"ESR_TO_GTV2MU2 ", "ESR extraction to TV2MU2"}}, + {525, {"GHTDMU1_TO_HTC ", "Experiment line to nuclear reaction experiments at HTC (exotic nuclei and nuclear dynamics)"}}, + {526, {"GTV2MU2_TO_GTV2MU3 ", "GTV2MU2 to GTV2MU3"}}, + {527, {"GTV2MU3_TO_HTA ", "Experiment line to HTA"}}, + {528, {"GTV2MU3_TO_GHTBMU1 ", "GTV2MU3 to GHTBMU1"}}, + {529, {"GHTBMU1_TO_HTP ", "Experiment line to HTP Dump"}}, + {530, {"GHTDMU1_TO_HTD ", "Experiment line to HTD"}}, + {531, {"GHTBMU1_TO_YRT1MH2 ", "GHTBMU1 to YRT1MH2"}}, + {592, {"UR_TO_GUH1MU2 ", "HSI LEBT Right Source"}}, + {593, {"UL_TO_GUH1MU2 ", "HSI LEBT Left Source"}}, + {595, {"UN_TO_GUN3BC1L ", "HLI LEBT"}}, + {608, {"GUH1MU2_TO_GUH2BC1L ", "GUH1MU2 to GUH2BC1L"}}, + {609, {"GUH2BC1L_TO_GUS3MK1 ", "HSI"}}, + {610, {"GUS3MK1_TO_GUS4MK6 ", "GUS3MK1 to GUS4MK6"}}, + {611, {"GUS3MK1_TO_GUS3MK2 ", "GUS3MK1 to GUS3MK2"}}, + {612, {"GUS3MK2_TO_GUS4MK6 ", "GUS3MK2 to GUS4MK6"}}, + {613, {"GUS3MK2_TO_US3 ", "Diagnostic line US3"}}, + {624, {"GUN3BC1L_TO_GUN6MU2 ", "HLI"}}, + {625, {"GUN6MU2_TO_GUS4MK6 ", "GUN6MU2 to GUS4MK6"}}, + {626, {"GUN6MU2_TO_UCW ", "Development line to CW"}}, + {640, {"GUS4MK6_TO_GUT1MK0 ", "Poststripper"}}, + {641, {"GUT1MK0_TO_GUT1MK1 ", "GUT1MK0 to GUT1MK1"}}, + {642, {"GUT1MK1_TO_GUT2MK2 ", "GUT1MK1 to GUT2MK2"}}, + {656, {"GUT1MK1_TO_GTK3MV1 ", "GUT1MK1 to GTK3MV1"}}, + {657, {"GTK3MV1_TO_GTK3MV4 ", "GTK3MV1 to GTK3MV4"}}, + {658, {"GTK3MV1_TO_GTK3MV3 ", "GTK3MV1 to GTK3MV3"}}, + {659, {"GTK3MV3_TO_GTK3MV4 ", "GTK3MV3 to GTK3MV4"}}, + {660, {"GTK3MV3_TO_TKD ", "Diagnostic line TKD"}}, + {661, {"GTK3MV4_TO_GTK7BC1L ", "GTK3MV4 to GTK7BC1L"}}, + {672, {"GUT1MK0_TO_GUM2MU5 ", "GUT1MK0 to GUM2MU5"}}, + {673, {"GUM2MU5_TO_UM1 ", "Experiment line to UM1"}}, + {674, {"GUM2MU5_TO_GUM3MU6 ", "GUM2MU5 to GUM3MU6"}}, + {675, {"GUM3MU6_TO_UM2 ", "Experiment line to UM2"}}, + {676, {"GUM3MU6_TO_UM3 ", "Experiment line to UM3"}}, + {677, {"GUT2MK2_TO_GUZCMU1 ", "GUT2MK2 to GUZCMU1"}}, + {678, {"GUZCMU1_TO_UZ6 ", "Experiment line to UZ6"}}, + {679, {"GUZCMU1_TO_UZ7 ", "Experiment line to UZ7"}}, + {680, {"GUT2MK2_TO_UY7 ", "Experiment line to UY7"}}, + {688, {"GUT2MK2_TO_GUXAMU3 ", "GUT2MK2 to GUXAMU3"}}, + {689, {"GUXAMU3_TO_GUXAMU4 ", "GUXAMU3 to GUXAMU4"}}, + {690, {"GUXAMU3_TO_GUXCMU3 ", "GUXAMU3 to GUXCMU3"}}, + {691, {"GUXAMU4_TO_GUXBMU5 ", "GUXAMU4 to GUXBMU5"}}, + {692, {"GUXAMU4_TO_UX3 ", "Experiment line to UX3"}}, + {693, {"GUXBMU5_TO_UX2 ", "Experiment line to UX2"}}, + {694, {"GUXCMU3_TO_GUXFMU1 ", "GUXCMU3 to GUXFMU1"}}, + {695, {"GUXCMU3_TO_UX6 ", "Experiment line to UX6"}}, + {696, {"GUXFMU1_TO_GUXHMU2 ", "GUXFMU1 to GUXHMU2"}}, + {697, {"GUXFMU1_TO_UX7 ", "Experiment line to UX7"}}, + {698, {"GUXHMU2_TO_UX0 ", "Experiment line to UX0"}}, + {699, {"GUXHMU2_TO_UX8 ", "Experiment line to UX8"}}, + {928, {"SIS18_B2B_EXTRACT ", "B2B internal: extraction from SIS18"}}, + {929, {"SIS18_B2B_ESR ", "B2B internal: transfer SIS18 to ESR"}}, + {930, {"SIS18_B2B_SIS100 ", "B2B internal: transfer SIS18 to SIS100"}}, + {933, {"ESR_B2B_EXTRACT ", "B2B internal: extraction from ESR"}}, + {934, {"ESR_B2B_CRYRING ", "B2B internal: transfer ESR to CRYRING"}}, + {938, {"CRYRING_B2B_EXTRACT ", "B2B internal: extraction from CRYRING"}}, + {944, {"SIS100_B2B_EXTRACT ", "B2B internal: extraction from SIS100"}}, + {0xFFF, {"LocalGroup", "never sent by data master"}} +}; +// https://www-acc.gsi.de/wiki/Timing/TimingSystemEventNumbers +static const std::map> eventNrTable{ + {0, {"EVT_PZ_ChanEnd", "SIS/ESR PZ only: mark end of channel"}}, + {1, {"EVT_START_RF", "Power to RF cavities"}}, + {2, {"EVT_START_IQ", "Begin of beam production"}}, + {3, {"EVT_IQ_HEATING", "Begin of ion source arc, ECR RF"}}, + {4, {"EVT_PREP_BEAM_ON", "Switch on chopper, read act. values"}}, + {5, {"EVT_IQ_GAS_ON", "Start of heading gas (ion source)"}}, + {6, {"EVT_BEAM_ON", "Valid beam"}}, + {8, {"EVT_BEAM_OFF", "End of beam production"}}, + {10, {"EVT_STOP_IQ", "Switch IQ off"}}, + {12, {"EVT_STOP_RF", "Switch RF off"}}, + {16, {"EVT_PREP_NEXT_ACC", "Prepare next acc., write set values"}}, + {17, {"EVT_AUX_PRP_NXT_ACC", "Set values in magnet prep. cycles"}}, + {18, {"EVT_RF_PREP_NXT_ACC", "Begin of RF extra cycle"}}, + {19, {"EVT_PREP_UNI_DIAG", "Prepare diagnostic devices, Unilac"}}, + {20, {"EVT_PREP_AUX", "Trigger BIF beam diagnostics"}}, + {21, {"EVT_UNLOCK_ALVAREZ", "Unlock A4 for next pulse"}}, + {22, {"EVT_PREP_EXP", "Pretrigger for Experiments"}}, + {24, {"EVT_RF_AUX_TRIG", "Trigger ADC in RF extra cycles"}}, + {25, {"EVT_MAGN_DOWN", "Set magnets to zero current"}}, + {26, {"EVT_SD_AUX_START", "Beam diagnostics aux start trigger"}}, + {27, {"EVT_SD_AUX_STOP", "Beam diagnostics aux stop trigger"}}, + {28, {"EVT_PRETRIG_BEAM", "Magnets on flattop, PG trigger"}}, + {29, {"EVT_UNI_END_CYCLE", "End of a UNILAC cycle"}}, + {30, {"EVT_READY_TO_SIS", "10 ms before beam transfer"}}, + {31, {"EVT_SRC_DST_ID", "Source/Destination of beam"}}, + {32, {"EVT_START_CYCLE", "First Event in a cycle"}}, + {33, {"EVT_START_BFELD", "Start of B-Field"}}, + {34, {"EVT_PEAK_UP", "Peaking trip up"}}, + {35, {"EVT_INJECT", "B-field reached injection level"}}, + {36, {"EVT_UNI_TRANS", "Demand UNILAC beam"}}, + {37, {"EVT_UNI_ACKN", "Acknowledge from Unilac"}}, + {38, {"EVT_UNI_READY", "Unilac ready, transfer in preparation"}}, + {39, {"EVT_MB_LOAD", "Start Bumper magnet ramp up"}}, + {40, {"EVT_MB_TRIGGER", "Start active Bumper Ramp (down)"}}, + {41, {"EVT_INJECT_END", "Start of injection from unilac"}}, + {42, {"EVT_TIMEOUT_1", "Trigger timeout channel 1"}}, + {43, {"EVT_RAMP_START", "Start of acc/deacc ramp in magnets"}}, + {44, {"EVT_PREP_INJ", "Prepare devices for next Injection"}}, + {45, {"EVT_FLATTOP", "Magnets reached Flattop"}}, + {46, {"EVT_EXTR_START_SLOW", "Start of extraction"}}, + {47, {"EVT_MK_LOAD_1", "Load Kicker for HF-triggered puls"}}, + {48, {"EVT_MK_LOAD_2", "Load Kicker for Timinggenerator-triggered puls"}}, + {49, {"EVT_KICK_START_1", "Start Kicker for HF-triggered extraction"}}, + {50, {"EVT_TIMEOUT_2", "Trigger timeout channel 2"}}, + {51, {"EVT_EXTR_END", "End of extraction"}}, + {52, {"EVT_FLATTOP_END", "End of Flattop (Magnets) reached"}}, + {53, {"EVT_PREP_EXTR", "Prepare devices for next Extraction"}}, + {54, {"EVT_PEAK_DOWN", "Peaking strip down"}}, + {55, {"EVT_END_CYCLE", "End of a cycle"}}, + {56, {"EVT_SYNCH", "Trigger all function generators"}}, + {57, {"EVT_EXTR_BUMP", "Start of closed orbit distortion"}}, + {58, {"EVT_SIS_ACK_TO_ESR", "SIS acknowledge to ESR"}}, + {59, {"EVT_SIS_READY_1", "SIS ready for extraction to ESR"}}, + {60, {"EVT_SIS_READY_2", "SIS ready for extraction to ESR"}}, + {61, {"EVT_TRANS_START_1", "Begin of transmission to ESR"}}, + {62, {"EVT_TRANS_START_2", "Begin of transmission to ESR"}}, + {63, {"EVT_PHASE_SYNCH_GATE_1", "Begin of first phase synchronisation between ESR- and SIS-HF"}}, + {64, {"EVT_TIMEOUT_3", "Trigger timeout channel 3"}}, + {65, {"EVT_PHASE_SYNCH_GATE_2", "Begin of second phase synchronisation between ESR- and SIS-HF"}}, + {66, {"EVT_TIMEOUT_4", "Trigger timeout channel 4"}}, + {67, {"EVT_TIMEOUT_5", "Trigger timeout channel 5"}}, + {68, {"EVT_TIMEOUT_6", "Trigger timeout channel 6"}}, + {69, {"EVT_KICK_START_2", "Start Kicker for TG-triggered extraction"}}, + {70, {"EVT_UNI_PREP", "Demand setting of TK (200 ms before beam)"}}, + {71, {"EVT_INJ_BUMP", "Closed orbit destortion for reinjection"}}, + {72, {"EVT_RE_INJ_START", "SIS is ready for reinjection"}}, + {73, {"EVT_RE_INJ_END", "End of reinjection"}}, + {74, {"EVT_PREP_RE_INJ", "Prepare devices for Reinjection"}}, + {75, {"EVT_PREP_KICK_1", "Prepare kicker: load capacitor 1"}}, + {76, {"EVT_PREP_KICK_2", "Prepare kicker: load capacitor 2"}}, + {77, {"EVT_MK_LOAD_RE_INJ", "Load Kicker for Reinjection"}}, + {78, {"EVT_EXTR_STOP_SLOW", "End of slow extraction"}}, + {79, {"EVT_ASYNC_TRANS", "Transfer to ESR without HF synchron."}}, + {80, {"EVT_EMA_START", "Start EMA meassurement gate"}}, + {81, {"EVT_HF_BM_START", "Vorbereitung Strahlgym."}}, + {82, {"EVT_HF_BM_AMPH2", "Start Ampl., Ph. Kav. 2 Strahlgym."}}, + {83, {"EVT_HF_BM_FREQ2", "Start Freq. Kav. 2 Strahlgym."}}, + {84, {"EVT_HF_BM_AMPH12", "Start Ampl., Ph. Kav. 1+2 Strahlgym."}}, + {85, {"EVT_HF_BM_FREQ1", "Start Freq. Kav. 1 Strahlgym."}}, + {86, {"EVT_HF_BM_AMPH1", "Start Ampl., Ph. Kav. 1 Strahlgym."}}, + {87, {"EVT_TG_CLEAR", "Clear Timinggenerator"}}, + {88, {"EVT_MQ_START1", "Load Q-Kicker (1st shot)"}}, + {89, {"EVT_MQ_START2", "Load Q-Kicker (2nd shot)"}}, + {90, {"EVT_MQ_MESS", "Trigger for Q-Wert measurement"}}, + {91, {"EVT_INT_FLAT", "Start of internal Flattop"}}, + {92, {"EVT_DT_MESS", "Trigger for DTML measurement"}}, + {93, {"EVT_DX_MESS", "Trigger for DX measurement"}}, + {94, {"EVT_TG_SWITCH", "Umschalten der TG synchronisation"}}, + {95, {"EVT_START_LECROY", "Start measurement with LeCroy"}}, + {96, {"EVT_GAP_POS_MESS", "Messevent zwischen den Zyklen"}}, + {97, {"EVT_GAP_TRA_MESS", "Messevent zwischen den Zyklen"}}, + {98, {"EVT_GAP_SCR_MESS", "Messevent zwischen den Zyklen"}}, + {99, {"EVT_GAP_DTS_MESS", "Messevent für schnelle Trafos"}}, + {100, {"EVT_SIS_TRANS1_ESR", "First transmission complete to ESR"}}, + {101, {"EVT_SIS_TRANS2_ESR", "Second transmission complete to ESR"}}, + {102, {"EVT_PREP_DIAGNOSE", "..."}}, + {103, {"EVT_PREP_DG", "Vorbereitung Gitterhardware"}}, + {104, {"EVT_DG_TRIGGER", "Trigger Messung Gitterhardware"}}, + {105, {"EVT_KICK_READY", "Ext. Synchronisat. für Kicker"}}, + {106, {"EVT_KICK_GATE", "Start externe Synchr. für Kicker"}}, + {107, {"EVT_PREP_DTI", "Entklemmung TK trafos SIS timing"}}, + {108, {"EVT_INJ_READY", "Einzelne Unilac-Injektion erfolgt"}}, + {109, {"EVT_MHB", "Multi-Harmonischer-Betrieb von GS00BE_S (SIS18)"}}, + {128, {"EVT_ESR_READY_1", "ESR ready for beam transfer"}}, + {129, {"EVT_ESR_READY_2", "ESR ready for beam transfer"}}, + {130, {"EVT_ESR_REQ_TO_SIS", "ESR beam request to SIS"}}, + {131, {"EVT_EIN1", "ESR ????? 1"}}, + {132, {"EVT_EIN2", "ESR ????? 2"}}, + {133, {"EVT_MAN1", "ESR manipulation 1"}}, + {134, {"EVT_MAN2", "ESR manipulation 2"}}, + {135, {"EVT_PHASE_SYNCH_1", "Phase is 1st time synchronized between ESR- and SIS-HF"}}, + {136, {"EVT_PHASE_SYNCH_2", "Phase is 2nd time synchronized between ESR- and SIS-HF"}}, + {137, {"EVT_NO_BEAM", "There is no beam in ESR (for diagnostics)"}}, + {138, {"EVT_DT_STOP", "Stop of DTML measurement"}}, + {139, {"EVT_DT_READ", "Read data for DTML measurement"}}, + {140, {"EVT_DX_STOP", "Stop of DX measurement"}}, + {141, {"EVT_DX_READ", "Read data for DX measurement"}}, + {142, {"EVT_LEXT", "ESR start with ?????????????"}}, + {143, {"EVT_PSTACK", "ESR start with ?????????????"}}, + {144, {"EVT_STACK", "ESR start with ?????????????"}}, + {145, {"EVT_ESR_TRANS_SIS", "Transmission complete to SIS"}}, + {146, {"EVT_ECE_HV_VAR", "Stepwise variation of ECE voltage"}}, + {147, {"EVT_ECE_HV_ON", "Write set value for pulsed ECE HV"}}, + {148, {"EVT_ECE_HV_MESS", "Read actual value of pulsed ECE HV"}}, + {149, {"EVT_BUNCH_ROTATE", "Start with bunch rotation"}}, + {150, {"EVT_ESR_REQ_REINJ", "ESR request SIS reinjection"}}, + {151, {"EVT_RESET", "Start of 11th Ramp in magnets"}}, + {152, {"EVT_AUS1", "Start of 11th Ramp in magnets"}}, + {153, {"EVT_AUS2", "Start of 11th Ramp in magnets"}}, + {154, {"EVT_RAMP_11", "Start of 11th Ramp in magnets"}}, + {155, {"EVT_RAMP_12", "Start of 12th Ramp in magnets"}}, + {156, {"EVT_RAMP_13", "Start of 13th Ramp in magnets"}}, + {157, {"EVT_RAMP_14", "Start of 14th Ramp in magnets"}}, + {158, {"EVT_RAMP_15", "Start of 15th Ramp in magnets"}}, + {159, {"EVT_RAMP_16", "Start of 16th Ramp in magnets"}}, + {160, {"EVT_EBEAM_ON", "Electron beam on"}}, + {161, {"EVT_EBEAM_OFF", "Electron beam off"}}, + {162, {"EVT_UDET_IN", "Move detector (charge changed) in"}}, + {163, {"EVT_UDET_OUT", "Move detector out"}}, + {180, {"EVT_TIMING_LOCAL", "Take local timing"}}, + {181, {"EVT_TIMING_EXTERN", "Switch to extern timing"}}, + {199, {"EVT_INTERNAL_FILL", "PZ: Fill long event delays (>10s)"}}, + {200, {"EVT_DATA_START", "First data transfer event"}}, + {201, {"EVT_DATA_0", "Data transfer event"}}, + {202, {"EVT_DATA_1", "Data transfer event"}}, + {203, {"EVT_DATA_2", "Data transfer event"}}, + {204, {"EVT_DATA_3", "Data transfer event"}}, + {205, {"EVT_DATA_4", "Data transfer event"}}, + {206, {"EVT_DATA_5", "Data transfer event"}}, + {207, {"EVT_DATA_6", "Data transfer event"}}, + {208, {"EVT_DATA_7", "Data transfer event"}}, + {209, {"EVT_TIME_1", "Time stamp, most sign. bits"}}, + {210, {"EVT_TIME_2", "Time stamp, medium sign. bits"}}, + {211, {"EVT_TIME_3", "Time stamp, least sign. bits"}}, + {224, {"EVT_UTC_1", "Time stamp UTC bits 32..39"}}, + {225, {"EVT_UTC_2", "Time stamp UTC bits 24..31"}}, + {226, {"EVT_UTC_3", "Time stamp UTC bits 16..23"}}, + {227, {"EVT_UTC_4", "Time stamp UTC bits 8..15"}}, + {228, {"EVT_UTC_5", "Time stamp UTC bits 0.. 7"}}, + {245, {"EVT_END_CMD_EXEC", "End of command evaluation within gap"}}, + {246, {"EVT_BEGIN_CMD_EXEC", "Start of command evaluation within gap"}}, + {247, {"EVT_GAP_INFO", "PZ information about next cycle"}}, + {248, {"EVT_COLL_DET", "PZ detected collision of 2 asynch. events"}}, + {249, {"EVT_TIMING_DIAG", "For diagnostics in timing system"}}, + {250, {"EVT_SUP_CYCLE_START", "Supercycle starts"}}, + {251, {"EVT_GET_EC_TIME", "Synchronous reading of time of all ECs"}}, + {252, {"EVT_SET_EC_TIME", "Synchronous setting of time in all ECs"}}, + {253, {"EVT_RESERVED", "In older systems : change vrtacc event"}}, + {254, {"EVT_EMERGENCY", "Emergency event"}}, + {255, {"EVT_COMMAND", "Command event"}}, + {256, {"CMD_BP_START", "Start of Beam Process"}}, + {257, {"CMD_SEQ_START", "Sequence start. Implicitly, a CMD_SEQ_START is also a CMD_BP_START"}}, + {258, {"CMD_GAP_START", "Start of gap window. Within this window, background tasks can be performed safely."}}, + {259, {"CMD_GAP_END", "End of gap window. Within this window, background tasks can be performed safely."}}, + {270, {"CMD_SOURCE_BEAM_ON", "Source Beam On"}}, + {271, {"CMD_SOURCE_BEAM_OFF", "Source Beam Off"}}, + {272, {"CMD_SOURCE_HV_PREP", "HV Preparation for the Source"}}, + {273, {"CMD_SOURCE_START", "Start beam production in source"}}, + {274, {"CMD_SOURCE_STOP", "Stop beam production in source and switch source to standby"}}, + {280, {"CMD_BI_TRIGGER", "Beam Diagnostics: Trigger"}}, + {281, {"CMD_BI_MEAS1", "Beam Diagnostics: Measure Event 1"}}, + {282, {"CMD_BI_MEAS2", "Beam Diagnostics: Measure Event 2"}}, + {283, {"CMD_BEAM_INJECTION", "Injection into a ring machine; played shortly prior to each injection within a sequence"}}, + {284, {"CMD_BEAM_EXTRACTION", "Extraction from a ring machine; played shortly prior to each extraction within a sequence"}}, + {285, {"CMD_START_ENERGY_RAMP", "Start acceleration or deceleration; only for beam-in processes; intended for diagnostic purposes"}}, + {286, {"CMD_CUSTOM_DIAG_1", "Intended for diagnostic purposes"}}, + {287, {"CMD_CUSTOM_DIAG_2", "Intended for diagnostic purposes"}}, + {288, {"CMD_CUSTOM_DIAG_3", "Intended for diagnostic purposes"}}, + {290, {"CMD_RF_PREP", "RF: Prepare device for pulse generation"}}, + {291, {"CMD_RF_PREP_PAUSE", "RF: Prepare device for pulse generation (paused operation and beam-out)"}}, + {292, {"CMD_RF_STOP_PAUSE", "RF: Stop generation of pulse (paused operation and beam-out)"}}, + {312, {"CMD_SYNCH", "Trigger all function generators"}}, + {313, {"CMD_RF_SWITCH_01", "Change RF system operation mode according to predefined settings"}}, + {314, {"CMD_RF_SWITCH_02", "Change RF system operation mode according to predefined settings"}}, + {315, {"CMD_RF_SWITCH_03", "Change RF system operation mode according to predefined settings"}}, + {316, {"CMD_RF_SWITCH_04", "Change RF system operation mode according to predefined settings"}}, + {317, {"CMD_RF_SWITCH_05", "Change RF system operation mode according to predefined settings"}}, + {318, {"CMD_RF_SWITCH_06", "Change RF system operation mode according to predefined settings"}}, + {319, {"CMD_RF_SWITCH_07", "Change RF system operation mode according to predefined settings"}}, + {320, {"CMD_RF_SWITCH_08", "Change RF system operation mode according to predefined settings"}}, + {321, {"CMD_RF_SWITCH_09", "Change RF system operation mode according to predefined settings"}}, + {322, {"CMD_RF_SWITCH_10", "Change RF system operation mode according to predefined settings"}}, + {323, {"CMD_RF_SWITCH_11", "Change RF system operation mode according to predefined settings"}}, + {324, {"CMD_RF_SWITCH_12", "Change RF system operation mode according to predefined settings"}}, + {325, {"CMD_RF_SWITCH_13", "Change RF system operation mode according to predefined settings"}}, + {326, {"CMD_RF_SWITCH_14", "Change RF system operation mode according to predefined settings"}}, + {327, {"CMD_RF_SWITCH_15", "Change RF system operation mode according to predefined settings"}}, + {328, {"CMD_RF_SWITCH_16", "Change RF system operation mode according to predefined settings"}}, + {329, {"CMD_RF_SWITCH_17", "Change RF system operation mode according to predefined settings"}}, + {330, {"CMD_RF_SWITCH_18", "Change RF system operation mode according to predefined settings"}}, + {331, {"CMD_RF_SWITCH_19", "Change RF system operation mode according to predefined settings"}}, + {332, {"CMD_RF_SWITCH_20", "Change RF system operation mode according to predefined settings"}}, + {333, {"CMD_RF_SWITCH_21", "Change RF system operation mode according to predefined settings"}}, + {334, {"CMD_RF_SWITCH_22", "Change RF system operation mode according to predefined settings"}}, + {335, {"CMD_RF_SWITCH_23", "Change RF system operation mode according to predefined settings"}}, + {336, {"CMD_RF_SWITCH_24", "Change RF system operation mode according to predefined settings"}}, + {337, {"CMD_RF_SWITCH_25", "Change RF system operation mode according to predefined settings"}}, + {338, {"CMD_RF_SWITCH_26", "Change RF system operation mode according to predefined settings"}}, + {339, {"CMD_RF_SWITCH_27", "Change RF system operation mode according to predefined settings"}}, + {340, {"CMD_RF_SWITCH_28", "Change RF system operation mode according to predefined settings"}}, + {341, {"CMD_RF_SWITCH_29", "Change RF system operation mode according to predefined settings"}}, + {342, {"CMD_RF_SWITCH_30", "Change RF system operation mode according to predefined settings"}}, + {343, {"CMD_RF_SWITCH_31", "Change RF system operation mode according to predefined settings"}}, + {344, {"CMD_RF_SWITCH_32", "Change RF system operation mode according to predefined settings"}}, + {345, {"CMD_RF_PHASE_RESET", "Trigger the DDS phase reset for RF devices"}}, + {350, {"CMD_UNI_TCREQ", "Request UNILAC PZ to reserve the transfer channel"}}, + {351, {"CMD_UNI_TCREL", "Request UNILAC PZ to release the transfer channel"}}, + {352, {"CMD_UNI_BREQ", "Request UNILAC PZ to deliver beam"}}, + {353, {"CMD_UNI_BPREP", "Request UNILAC PZ to prepare beam delivery"}}, + {354, {"CMD_UNI_BREQ_NOWAIT", "Request UNILAC PZ to deliver beam but without 'wait block' in Data Master schedule"}}, + {512, {"CMD_FG_PREP", "Prepare function generator"}}, + {513, {"CMD_FG_START", "Start function generator"}}, + {518, {"CMD_BEAM_ON", "General: Begin of beam passage"}}, + {520, {"CMD_BEAM_OFF", "General: End of beam passage"}}, + {521, {"CMD_SEPTUM_CHARGE", "Start septum ramp up"}}, + {522, {"CMD_EBEAM_ON", "Electron beam on"}}, + {523, {"CMD_EBEAM_OFF", "Electron beam off"}}, + {524, {"CMD_STOCH_COOLING_ON", "Stochastic cooling on"}}, + {525, {"CMD_STOCH_COOLING_OFF", "Stochastic cooling off"}}, + {526, {"CMD_TARGET_ON", "Enable target"}}, + {527, {"CMD_TARGET_OFF", "Disable target"}}, + {528, {"CMD_EXP_DAQ_START", "Start experiment data acquisition"}}, + {529, {"CMD_EXP_DAQ_STOP", "Stop experiment data acquisition"}}, + {530, {"CMD_EXP_DRIVE_IN", "Move experiment actuators (detectors) to measurement position"}}, + {531, {"CMD_EXP_DRIVE_OUT", "Move experiment actuators (detectors) to outside position (clear beam aperture)"}}, + {532, {"CMD_DRIVE_MOVE", "Move timing-controlled actuators to new position"}}, + {539, {"CMD_CHOPPER_CHARGE", "Start energizing chopper"}}, + {1024, {"CMD_BUMPER_CHARGE", "Start bumper magnet ramp up"}}, + {1025, {"CMD_BUMPER_START", "Start bumper ramp down"}}, + {1026, {"CMD_INJ_KICKER_START", "Start injection kicker"}}, + {1027, {"CMD_EXTR_KICKER_START", "Start extraction kicker"}}, + {2048, {"CMD_B2B_PMEXT", "B2B internal: request phase measurement (extraction)"}}, + {2049, {"CMD_B2B_PMINJ", "B2B internal: request phase measurement (injection)"}}, + {2050, {"CMD_B2B_PREXT", "B2B internal: send result of phase measurement (extraction)"}}, + {2051, {"CMD_B2B_PRINJ", "B2B internal: send result of phase measurement (injection)"}}, + {2052, {"CMD_B2B_TRIGGEREXT", "B2B: trigger kicker electronics (extraction)"}}, + {2053, {"CMD_B2B_TRIGGERINJ", "B2B: trigger kicker electronics (injection)"}}, + {2054, {"CMD_B2B_DIAGKICKEXT", "B2B: send result of kick diagnostic (extraction)"}}, + {2055, {"CMD_B2B_DIAGKICKINJ", "B2B: send result of kick diagnostic (injection)"}}, + {2056, {"CMD_B2B_DIAGEXT", "B2B internal: send result of optional diagnostic (extraction)"}}, + {2057, {"CMD_B2B_DIAGINJ", "B2B internal: send result of optional diagnostic (injection)"}}, + {2079, {"CMD_B2B_START", "Start B2B procedure"}}, + {4000, {"CMD_GMT_INTERNAL1", "internal use by the GMT"}} +}; + +static const std::map, std::less<>> outputConfig{ + // {Output Name} -> {Display name, show column by default} + {"IO1", {"1", true}}, + {"IO2", {"2", true}}, + {"IO3", {"3", true}}, + {"LED1_ADD_R", {"R", true}}, + {"LED1_BASE_R", {"RB", false}}, + {"LED2_ADD_B", {"B", true}}, + {"LED2_BASE_B", {"BB", false}}, + {"LED3_ADD_G", {"G", true}}, + {"LED3_BASE_G", {"GB", false}}, + {"LED4_ADD_W", {"W", true}}, + {"LED4_BASE_W", {"WB", false}}, +}; + +static const std::map> demoSchedules = {{"Pattern 1", R"( +0x1136100000000001 0x0 100000000 +0x1136100c00200041 0x140000000000 200000000 +0x1136100800200081 0x140000000000 300000000 +0x113611b800200081 0x140000000000 320000000 +0x11361008002000c1 0x140000000000 500000000 +0x1136100000200141 0x140000000000 530000000 +0x1136100000700081 0xc0000000000 570000000 +0x1136100c007000c3 0xc0000000000 600000000 +0x1136100800700143 0xc0000000000 700000000 +0x113611e800700143 0xc0000000000 800000000 +0x1136100800700183 0xc0000000000 900000000 +0x1136100800700203 0xc0000000000 1000000000 +0x1136100800900043 0xc0000000000 1100000000 +0x1136100800900103 0xc0000000000 1200000000 +0x11361008009001c3 0xc0000000000 1300000000 +0x1136100000900243 0x140000000000 1400000000 +)"}}; + +#endif //GR_DIGITIZERS_EVENT_DEFINITIONS_HPP diff --git a/blocklib/timing/src/fairPlot.hpp b/blocklib/timing/src/fairPlot.hpp new file mode 100644 index 00000000..60d00cc6 --- /dev/null +++ b/blocklib/timing/src/fairPlot.hpp @@ -0,0 +1,155 @@ +#ifndef GR_DIGITIZERS_FAIRPLOT_HPP +#define GR_DIGITIZERS_FAIRPLOT_HPP +#include +#include +#include "event_definitions.hpp" +/* + * ImPlot Extension to display data with different discrete (or continuous) values as a color coded horizontal bar + * - for discrete values, show the discrete values either inline or show a legend + * - for continues values show a color gradient scale + */ +#ifndef IMPLOT_NO_FORCE_INLINE +#ifdef _MSC_VER +#define IMPLOT_INLINE __forceinline +#elif defined(__GNUC__) +#define IMPLOT_INLINE inline __attribute__((__always_inline__)) +#elif defined(__CLANG__) +#if __has_attribute(__always_inline__) + #define IMPLOT_INLINE inline __attribute__((__always_inline__)) + #else + #define IMPLOT_INLINE inline + #endif + #else + #define IMPLOT_INLINE inline +#endif +#else +#define IMPLOT_INLINE inline +#endif + +namespace FairPlot { + template + struct ScrollingBuffer { + std::array data{}; + std::size_t size = 0; + std::size_t offset = 0; + + bool empty() const { return size == 0; } + + void pushBack(ImPlotPoint newData) { + data[(offset + size) % N] = newData; + if (size < N) { + size++; + } else { + offset++; + } + } + + void reset() { + size = 0; + offset = 0; + } + + const ImPlotPoint &operator[](std::size_t idx) const { + return data[(idx + offset) % N]; + } + }; + + template + void plotStatusBar(const char* label_id, const FairPlot::ScrollingBuffer &pointBuffer, double xOffset) { + if (ImPlot::BeginItem(label_id, 0, ImPlotCol_Fill)) { + ImPlotContext& gp = *ImPlot::GetCurrentContext(); + ImDrawList& draw_list = *ImPlot::GetPlotDrawList(); + if (const ImPlotNextItemData& s = ImPlot::GetItemData(); pointBuffer.size > 1 && s.RenderFill) { + const ImPlotPlot& plot = *gp.CurrentPlot; + const ImPlotAxis& x_axis = plot.Axes[plot.CurrentX]; + const ImPlotAxis& y_axis = plot.Axes[plot.CurrentY]; + + const auto pixY_0 = static_cast(s.LineWeight); + const auto pixY_1 = static_cast(s.DigitalBitHeight); + const auto pixY_chPosOffset = static_cast(ImMax(s.DigitalBitHeight, s.DigitalBitHeight) + s.DigitalBitGap); + const int pixY_Offset = 1; //20 pixel from bottom due to mouse cursor label + + int pixYMax = 0; + ImPlotPoint itemData1 = pointBuffer[0]; + for (std::size_t i = 0; i <= pointBuffer.size; ++i) { + ImVec2 pMin; + ImVec2 pMax; + ImPlotPoint itemData2; + if (i < pointBuffer.size) { + itemData2 = pointBuffer[i]; + if (ImNanOrInf(itemData1.y)) { + itemData1 = itemData2; + continue; + } + if (ImNanOrInf(itemData2.y)) itemData2.y = ImConstrainNan(ImConstrainInf(itemData2.y)); + pixYMax = ImMax(pixYMax, pixY_chPosOffset); + pMin = ImPlot::PlotToPixels({itemData1.x + xOffset, itemData1.y}, IMPLOT_AUTO, IMPLOT_AUTO); + pMax = ImPlot::PlotToPixels({itemData2.x + xOffset, itemData2.y}, IMPLOT_AUTO, IMPLOT_AUTO); + pMin.y = (y_axis.PixelMin) + (static_cast((-gp.DigitalPlotOffset) - pixY_Offset)); + pMax.y = (y_axis.PixelMin) + (static_cast((-gp.DigitalPlotOffset) - pixY_0 - pixY_1 - pixY_Offset)); + //plot only one rectangle for same digital state + if (itemData1.y == itemData2.y && i + 1 < pointBuffer.size) { + continue; + } + //do not extend plot outside plot range + if (pMin.x < x_axis.PixelMin) pMin.x = x_axis.PixelMin; + if (pMax.x < x_axis.PixelMin) pMax.x = x_axis.PixelMin; + if (pMin.x > x_axis.PixelMax) pMin.x = x_axis.PixelMax; + if (pMax.x > x_axis.PixelMax) pMax.x = x_axis.PixelMax; + } else { // extend last event to the end of the plot + pMin = ImPlot::PlotToPixels({itemData1.x + xOffset, itemData1.y}, IMPLOT_AUTO, IMPLOT_AUTO); + pMin.y = (y_axis.PixelMin) + (static_cast((-gp.DigitalPlotOffset) - pixY_Offset)); + pMax = {x_axis.PixelMax, y_axis.PixelMin + (static_cast((-gp.DigitalPlotOffset) - pixY_0 - pixY_1 - pixY_Offset))}; + } + //plot a rectangle that extends up to x2 with y1 height + if ((pMax.x > pMin.x) && (gp.CurrentPlot->PlotRect.Contains(pMin) || gp.CurrentPlot->PlotRect.Contains(pMax))) { + ImColor color = ImPlot::GetColormapColorU32(static_cast(itemData1.y) % ImPlot::GetColormapSize(), IMPLOT_AUTO); + draw_list.AddRectFilled(pMin, pMax, color); + } + itemData1 = itemData2; + } + gp.DigitalPlotItemCnt++; + gp.DigitalPlotOffset += pixYMax; + } + ImPlot::EndItem(); + } + } + + template + void plotNamedEvents(const char* label_id, const FairPlot::ScrollingBuffer &pointBuffer, ImPlotInfLinesFlags_ flags, double xOffset) { + const double yText = ImPlot::GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO).Y.Max; + const ImVec2 textOffset{-8.f, 120.f}; + std::array data; + for (std::size_t i = 0; i < pointBuffer.size; i++) { + const ImPlotPoint p{pointBuffer[i].x + xOffset, pointBuffer[i].y}; + auto eventno = static_cast(p.y); + data[i] = p.x; + ImPlot::PlotText(fmt::format("{}({})", eventNrTable.contains(eventno) ? eventNrTable.at(eventno).first : "UNKNOWN", eventno).c_str(), p.x, yText, textOffset, ImPlotTextFlags_Vertical); + } + ImPlot::PlotInfLines(label_id, data.data(), static_cast(pointBuffer.size), flags, 0, sizeof(double)); + } + + int bpcidColormap() { + static std::array colorData = [&bpcidColors = bpcidColors]() { + std::array colors{}; + for (std::size_t i = 0; i < bpcidColors.size(); i++) + colors[i] = bpcidColors[i]; + return colors; + }(); + return ImPlot::AddColormap("BPCIDColormap", colorData.data(), colorData.size()); + } + int bpidColormap() { + static std::array colorData{0xff7dcfb6, 0xff00b2ca}; + return ImPlot::AddColormap("BPIDColormap", colorData.data(), colorData.size()); + } + int sidColormap() { + static std::array colorData{0xfff79256, 0xfffbd1a2}; + return ImPlot::AddColormap("SIDColormap", colorData.data(), colorData.size()); + } + int boolColormap() { + static std::array colorData{0x800000ff, 0x8000ff00}; + return ImPlot::AddColormap("BoolColormap", colorData.data(), colorData.size()); + } +} + +#endif //GR_DIGITIZERS_FAIRPLOT_HPP diff --git a/blocklib/timing/src/fair_header.h b/blocklib/timing/src/fair_header.h new file mode 100644 index 00000000..bc8fa8e4 --- /dev/null +++ b/blocklib/timing/src/fair_header.h @@ -0,0 +1,83 @@ +#ifndef IMPLOT_VISUALIZATION_FAIR_HEADER_H +#define IMPLOT_VISUALIZATION_FAIR_HEADER_H +#include +#include +#include +#include +#include + +CMRC_DECLARE(ui_assets); + +namespace app_header { +namespace detail { + void TextCentered(const std::string_view text) { + auto windowWidth = ImGui::GetWindowSize().x; + auto textWidth = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; + ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); + ImGui::Text("%s", text.data()); + } + + void TextRight(const std::string_view text) { + auto windowWidth = ImGui::GetWindowSize().x; + auto textWidth = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; + ImGui::SetCursorPosX(windowWidth - textWidth - ImGui::GetStyle().ItemSpacing.x); + ImGui::Text("%s", text.data()); + } +} + +ImFont* loadHeaderFont(float size) { + ImFontConfig config; + config.OversampleH = 1; + config.OversampleV = 1; + config.PixelSnapH = true; + config.FontDataOwnedByAtlas = false; + ImGuiIO &io = ImGui::GetIO(); + auto primaryFont = cmrc::ui_assets::get_filesystem().open("Roboto-Medium.ttf"); + return io.Fonts->AddFontFromMemoryTTF(const_cast(primaryFont.begin()), static_cast(primaryFont.size()), size, &config); +} + +void draw_header_bar(std::string_view title, ImFont *headerFont) { + using namespace detail; + // localtime + const auto clock = std::chrono::system_clock::now(); + const auto utcClock = fmt::format("{:%Y-%m-%d %H:%M:%S (LOC)}", std::chrono::round(clock)); + const auto utcStringSize = ImGui::CalcTextSize(utcClock.c_str()); + + const auto topLeft = ImGui::GetCursorPos(); + // draw title + ImGui::PushFont(headerFont); + // suppress title if it doesn't fit or is likely to overlap + if (0.5f * ImGui::GetIO().DisplaySize.x > (0.5f * ImGui::CalcTextSize(title.data()).x + utcStringSize.x)) { + TextCentered(title); + } + ImGui::PopFont(); + + ImGui::SameLine(); + auto pos = ImGui::GetCursorPos(); + TextRight(utcClock); // %Z should print abbreviated timezone but doesnt + // utc (using c-style timedate functions because of missing stdlib support) + // const auto utc_clock = std::chrono::utc_clock::now(); // c++20 timezone is not implemented in gcc or clang yet + // date + tz library unfortunately doesn't play too well with emscripten/fetchcontent + // const auto localtime = fmt::format("{:%H:%M:%S (%Z)}", date::make_zoned("utc", clock).get_sys_time()); + std::string utctime; // assume maximum of 32 characters for datetime length + utctime.resize(32); + pos.y += ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetCursorPos(pos); + const auto utc = std::chrono::system_clock::to_time_t(clock); + struct tm localtime{}; + const auto len = strftime(utctime.data(), utctime.size(), "%H:%M:%S (UTC)", gmtime_r(&utc, &localtime)); + TextRight(std::string_view(utctime.data(), len)); + auto posBeneathClock = ImGui::GetCursorPos(); + + // draw fair logo -> do not draw logo for simple debug app to not require external image loading dependencies and assets + ImGui::SetCursorPos(topLeft); + ImGui::PushFont(headerFont); + ImGui::TextUnformatted("FAIR"); + ImGui::PopFont(); + + posBeneathClock.x = 0.f; + ImGui::SetCursorPos(posBeneathClock); // set to end of image +} + +} // namespace app_header +#endif // IMPLOT_VISUALIZATION_FAIR_HEADER_H diff --git a/blocklib/timing/src/test-timing.cpp b/blocklib/timing/src/test-timing.cpp new file mode 100644 index 00000000..8f4b2d4d --- /dev/null +++ b/blocklib/timing/src/test-timing.cpp @@ -0,0 +1,838 @@ +#include +#include +#include +#include +#include +#include + +// UI +#include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#else +#include +#endif +#include +#include +#include +#include +#include "ImScoped.hpp" + +#include +#include +#include + +#include +#include +#include "fair_header.h" +#include "fairPlot.hpp" +#include "event_definitions.hpp" + +enum class InjectState { STOPPED, RUNNING, SINGLE }; +static constexpr uint64_t max_uint64 = std::numeric_limits::max(); +static constexpr uint64_t max_uint42 = (1UL << 42) - 1; +static constexpr uint64_t max_uint22 = (1UL << 22) - 1; +static constexpr uint64_t max_uint14 = (1UL << 14) - 1; +static constexpr uint64_t max_uint12 = (1UL << 12) - 1; +static constexpr uint64_t max_uint4 = (1UL << 4) - 1; +static constexpr double minDouble = 0; +static constexpr double maxDouble = std::numeric_limits::max(); + +template +void tableColumnString(const fmt::format_string &fmt, T&&... args) { + if (ImGui::TableNextColumn()) { + ImGui::TextUnformatted(fmt::format(fmt, std::forward(args)...).c_str()); + } +} +template +void tableColumnStringColor(ImColor color, const fmt::format_string &fmt, T&&... args) { +if (ImGui::TableNextColumn()) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, color); + ImGui::TextUnformatted(fmt::format(fmt, std::forward(args)...).c_str()); + } +} +void tableColumnBool(bool state, ImColor trueColor, ImColor falseColor) { + if (ImGui::TableNextColumn()) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, state ? trueColor : falseColor); + ImGui::TextUnformatted(fmt::format("{}", state ? "y" : "n").c_str()); + } +} +template +void tableColumnSlider(const std::string &id, T &field, float width) { + static constexpr T min_T = 0; + static constexpr T max_T = max; + if (ImGui::TableNextColumn()) { + ImGui::SetNextItemWidth(width); + uint64_t tmp = field; + ImGui::DragScalar(id.c_str(), ImGuiDataType_U64, &tmp, 1.0f, &min_T, &max_T, "%d", ImGuiSliderFlags_None); + field = tmp & max; + } +} +template +void sliderScaled(const std::string &id, T &field) { + static constexpr T min_T = 0; + static constexpr T max_T = max; + uint64_t tmp = field / scale; + ImGui::DragScalar(id.c_str(), ImGuiDataType_U64, &tmp, 1.0f, &min_T, &max_T, "%d", ImGuiSliderFlags_None); + field = (tmp * scale) & max; +} +template +void tableColumnSliderScaled(const std::string &id, T &field, float width) { + if (ImGui::TableNextColumn()) { + ImGui::SetNextItemWidth(width); + sliderScaled(id, field); + } +} +void tableColumnCheckbox(const std::string &id, bool &field) { + if (ImGui::TableNextColumn()) { + bool flag_beamin = field; + ImGui::Checkbox(id.c_str(), &flag_beamin); + field = flag_beamin; + } +} + +template +std::size_t getStableColorIndex(T id, std::map &colors, std::size_t colormapSize) { + if (colors.contains(id)) { + return colors[id]; + } else { + if (colormapSize - 1 > colors.size()) { + colors.insert({id, colors.size()}); + return colors[id]; + } + } + return colormapSize - 1; +} + +template +std::size_t getStableBPCIDColorIndex(T id) { + static std::map colors{}; + return getStableColorIndex(id, colors, bpcidColors.size()); +} + +template +ImColor getStableBPCIDColor(T id) { + return ImColor{bpcidColors[getStableBPCIDColorIndex(static_cast(id))]}; +} + +std::pair TimingGroupFilterDropdown() { + static int current = 0; + static const uint64_t mask = ((1ULL << 16) - 1 ) << (64-16); + auto getTimingMask = [&mask_ = mask](uint64_t gid){ + uint64_t id = ((gid & ((1UL << 12) - 1)) << 48) + + ((1 & ((1UL << 4) - 1)) << 60); + return std::pair {id, mask_}; + }; + static int radioButtonState = 0; + ImGui::RadioButton("All Timing Groups", &radioButtonState, 0); ImGui::SameLine(); + ImGui::RadioButton("SIS18", &radioButtonState, 1); ImGui::SameLine(); + ImGui::RadioButton("SIS100", &radioButtonState, 2); ImGui::SameLine(); + ImGui::RadioButton("ESR", &radioButtonState, 3); ImGui::SameLine(); + ImGui::RadioButton("CRYRING", &radioButtonState, 4); ImGui::SameLine(); + ImGui::RadioButton("Other:", &radioButtonState, 5); ImGui::SameLine(); + static std::vector items{}; + static std::vector displayStrings{timingGroupTable.size() - 4}; + static std::vector result = [&itms = items, &dispStrings = displayStrings]() { + std::vector res{}; + for (const auto & [gid, strings] : timingGroupTable) { + if (gid == 300 /*SIS18*/ || gid == 310 /*SIS100*/ || gid == 210 /*CRYRING*/ || gid == 340 /*ESR*/) { + continue; + } + auto & [enumName, description] = strings; + dispStrings.push_back(fmt::format("{} ({})", enumName, gid)); + itms.push_back(dispStrings.back().c_str()); + uint64_t id = ((gid & ((1UL << 12) - 1)) << 48) + + ((1 & ((1UL << 4) - 1)) << 60); + res.emplace_back(id); + } + return res; + }(); + ImGui::SetNextItemWidth(200); + { + auto _ = ImScoped::Disabled(radioButtonState != 5); + ImGui::Combo("##TimingGroup", ¤t, items.data(), static_cast(items.size())); + } + switch (radioButtonState) { + case 0: + return {0,0}; + case 1: + return getTimingMask(300); + case 2: + return getTimingMask(310); + case 3: + return getTimingMask(340); + case 4: + return getTimingMask(210); + default: + return {result[static_cast(current)], mask}; + } +} + +void drawSnoopedEventTableRow(const Timing::Event &evt, Timing &timing) { + ImGui::TableNextRow(); + ImGui::PushID(&evt); + tableColumnString("{}", taiNsToUtc(evt.time)); + tableColumnString("{}", taiNsToUtc(evt.executed)); + tableColumnStringColor(getStableBPCIDColor(evt.bpcid),"{}", evt.bpcid); + tableColumnString("{}", evt.sid); + tableColumnString("{}", evt.bpid); + tableColumnString("{1}({0})", evt.gid, timingGroupTable.contains(evt.gid) ? timingGroupTable.at(evt.gid).first : "UNKNOWN"); + tableColumnString("{1}({0})", evt.eventNo, eventNrTable.contains(evt.eventNo) ? eventNrTable.at(evt.eventNo).first : "UNKNOWN"); + tableColumnBool(evt.flagBeamin, ImGui::GetColorU32({0, 1.0, 0, 0.4f}), ImGui::GetColorU32({1.0, 0, 0, 0.4f})); + tableColumnBool(evt.flagBpcStart, ImGui::GetColorU32({0, 1.0, 0, 0.4f}), ImGui::GetColorU32({1.0, 0, 0, 0.4f})); + tableColumnBool(evt.reqNoBeam, ImGui::GetColorU32({0,1.0,0,0.4f}), ImGui::GetColorU32({1.0,0,0,0.4f})); + tableColumnString("{}", evt.virtAcc); + tableColumnString("{}", evt.bpcts); + tableColumnString("{}", evt.fid); + tableColumnString("{:#08x}", evt.id()); + tableColumnString("{:#08x}", evt.param()); + tableColumnBool(evt.flagReserved1, ImGui::GetColorU32({0, 1.0, 0, 0.4f}), ImGui::GetColorU32({1.0, 0, 0, 0.4f})); + tableColumnBool(evt.flagReserved2, ImGui::GetColorU32({0, 1.0, 0, 0.4f}), ImGui::GetColorU32({1.0, 0, 0, 0.4f})); + tableColumnBool(evt.reserved, ImGui::GetColorU32({0,1.0,0,0.4f}), ImGui::GetColorU32({1.0,0,0,0.4f})); + if (ImGui::TableNextColumn()) { // flags + // print flags + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, evt.flags ? ImGui::GetColorU32({1.0,0,0,0.4f}) : ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TableRowBg])); + auto delay = (static_cast(evt.executed - evt.time)) * 1e-6; + if (evt.flags & 1) { + ImGui::Text("%s", fmt::format(" !late (by {} ms)", delay).c_str()); + } else if (evt.flags & 2) { + ImGui::Text("%s", fmt::format(" !early (by {} ms)", delay).c_str()); + } else if (evt.flags & 4) { + ImGui::Text("%s", fmt::format(" !conflict (delayed by {} ms)", delay).c_str()); + } else if (evt.flags & 8) { + ImGui::Text("%s", fmt::format(" !delayed (by {} ms)", delay).c_str()); + } + } + ImGui::TableNextColumn(); + if (ImGui::Button("add to Schedule##addSchedule")) { + timing.events.emplace_back(100000000ULL + (timing.events.empty() ? 0UL : timing.events.back().time), evt.id(), evt.param()); + } + ImGui::PopID(); +} + +void showTimingEventTable(Timing &timing) { + static gr::BufferReader auto event_reader = timing.snooped.new_reader(); + if (ImGui::Button("clear")) { + std::ignore = event_reader.consume(event_reader.available()); + } + ImGui::SameLine(); ImGui::Dummy({50,5});ImGui::SameLine(); + auto [id_filter, mask] = TimingGroupFilterDropdown(); + if (id_filter != timing.snoopID || mask != timing.snoopMask) { + timing.snoopID = id_filter; + timing.snoopMask = mask; + timing.updateSnoopFilter(); + } + if (ImGui::CollapsingHeader("Received Timing Events", ImGuiTreeNodeFlags_DefaultOpen)) { + static int freeze_cols = 1; + static int freeze_rows = 1; + + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + ImVec2 outer_size{0.0f, TEXT_BASE_HEIGHT * 20}; + static ImGuiTableFlags flags = + ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + if (auto _ = ImScoped::Table("received_events", 20, flags, outer_size, 0.f)) { + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("timestamp", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze() + ImGui::TableSetupColumn("executed at", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("bpcid"); + ImGui::TableSetupColumn("sid"); + ImGui::TableSetupColumn("bpid"); + ImGui::TableSetupColumn("gid"); + ImGui::TableSetupColumn("eventno"); + ImGui::TableSetupColumn("beam-in"); + ImGui::TableSetupColumn("bpc-start"); + ImGui::TableSetupColumn("req no beam"); + ImGui::TableSetupColumn("virt acc"); + ImGui::TableSetupColumn("bpcts"); + ImGui::TableSetupColumn("fid", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("id", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("param", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved1", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved2", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("flags"); + ImGui::TableSetupColumn("##addToSchedule"); + + ImGui::TableHeadersRow(); + auto data = event_reader.get(); + + for (const auto &evt : std::ranges::reverse_view{data}) { + drawSnoopedEventTableRow(evt, timing); + } + if (data.size() > event_reader.buffer().size() / 2) { + std::ignore = event_reader.consume(data.size() - event_reader.buffer().size() / 2); + } + } + } +} + + +bool drawScheduledEventTableRow(Timing::Event &ev, Timing &timing, uint64_t default_offset) { + bool to_remove = false; + ImGui::PushID(&ev); + ImGui::TableNextRow(); + tableColumnSliderScaled("[ms]###time", ev.time, 80.f); + tableColumnSlider("##bpcid", ev.bpcid, 40.f); + tableColumnSlider("##sid", ev.sid, 40.f); + tableColumnSlider("##pbid", ev.bpid, 40.f); + tableColumnSlider("##gid", ev.gid, 40.f); + ImGui::SameLine(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", fmt::format("{}", timingGroupTable.contains(ev.gid) ? timingGroupTable.at(ev.gid).first : "UNKNOWN").c_str()); + } + tableColumnSlider("##eventno", ev.eventNo, 40.f); + ImGui::SameLine(); + ImGui::TextUnformatted(fmt::format("{}", eventNrTable.contains(ev.eventNo) ? eventNrTable.at(ev.eventNo).first : "UNKNOWN").c_str()); + tableColumnCheckbox("##beamin", ev.flagBeamin); + tableColumnCheckbox("##bpcstart", ev.flagBpcStart); + tableColumnCheckbox("##reqNoBeam", ev.reqNoBeam); + tableColumnSlider("##virtAcc", ev.virtAcc, 40.f); + tableColumnSlider("##bpcts", ev.bpcts, 80.f); + tableColumnSlider("##fid", ev.fid, 40.f); + tableColumnString("{:#08x}", ev.id()); + tableColumnString("{:#08x}", ev.param()); + tableColumnCheckbox("##reserved1", ev.flagReserved1); + tableColumnCheckbox("##reserved2", ev.flagReserved2); + tableColumnCheckbox("##reserved", ev.reserved); + // interactive settings + ImGui::TableNextColumn(); + if (ImGui::Button("remove")) { + to_remove = true; + } + ImGui::TableNextColumn(); + if (ImGui::Button("inject")) { + timing.injectEvent(ev, + timing.currentTimeTAI() + default_offset); + } + auto trigger = timing.triggers.contains(ev.id()) ? std::optional{timing.triggers[ev.id()]} : std::optional{}; + for (const auto & [i, outputName, _2] : timing.outputs) { + ImGui::TableNextColumn(); + auto [name, _3] = outputConfig.at(outputName); + if (trigger) { + ImGui::Checkbox(fmt::format("##{}", name).c_str(), &trigger->outputs[i]); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", fmt::format("Output: {}", outputName).c_str()); + } + } else { + bool output_selected = false; + ImGui::Checkbox(fmt::format("##{}", name).c_str(), &output_selected); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", fmt::format("Output: {}", outputName).c_str()); + } + if (output_selected) { + trigger = Timing::Trigger{}; + trigger->id = ev.id(); + trigger->outputs[i] = true; + trigger->delay = 0.0; + trigger->flattop = 0.003; + } + } + } + ImGui::TableNextColumn(); + if (trigger) { + ImGui::SetNextItemWidth(80); + ImGui::DragScalar("[ms]###delay", ImGuiDataType_Double, &trigger->delay, 1.0f, &minDouble, &maxDouble, "%f", ImGuiSliderFlags_None); + } + ImGui::TableNextColumn(); + if (trigger) { + ImGui::SetNextItemWidth(80); + ImGui::DragScalar("[ms]###flattop", ImGuiDataType_Double, &trigger->flattop, 1.0f, &minDouble, &maxDouble, "%f", ImGuiSliderFlags_None); + } + if (trigger) { + timing.updateTrigger(*trigger); + } + ImGui::PopID(); + return to_remove; +} + +void scheduleUpcomingEvents(Timing &timing, size_t ¤t, uint64_t &time_offset, InjectState &injectState) { + using enum InjectState; + if (injectState == RUNNING || injectState == SINGLE) { + if (current >= timing.events.size()) { + injectState = STOPPED; + } else { + while (timing.events[current].time + time_offset < timing.currentTimeTAI() + 500000000UL) { + auto ev = timing.events[current]; + timing.injectEvent(ev, time_offset); + if (current + 1 >= timing.events.size()) { + if (injectState == SINGLE) { + injectState = STOPPED; + break; + } else { + time_offset += timing.events[current].time; + current = 0; + } + } else { + ++current; + } + } + } + } +} + +void startStopButtons(Timing &timing, size_t ¤t, uint64_t &time_offset, InjectState &injectState) { + using enum InjectState; + ImGui::SameLine(); + if (injectState == RUNNING) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.4f, 0.4f, 1.0f,1.0f}); + if (ImGui::Button("Stop###StartStop")) { + injectState = STOPPED; + } + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("Start###StartStop")) { + current = 0; + time_offset = timing.currentTimeTAI(); + injectState = RUNNING; + } + } + ImGui::SameLine(); + if (injectState == SINGLE) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.4f, 0.4f, 1.0f,1.0f}); + if (ImGui::Button("Stop###SingleStop")) { + injectState = STOPPED; + } + ImGui::PopStyleColor(); + } else { + auto _ = ImScoped::Disabled(injectState != STOPPED); + if (ImGui::Button("Single###SingleStop")) { + current = 0; + time_offset = timing.currentTimeTAI(); + injectState = SINGLE; + } + } +} + +void saveLoadEvents(Timing &timing) { + ImGui::Button("Load"); + if (ImGui::BeginPopupContextItem("load schedule popup", ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::Button("from Clipboard")) { + Timing::Event::loadEventsFromString(timing.events, ImGui::GetClipboardText()); + } + for (const auto & [scheduleName, schedule] : demoSchedules) { + if (ImGui::Button(scheduleName.c_str())) { + Timing::Event::loadEventsFromString(timing.events, schedule); + } + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Save to Clipboard")) { + std::string string; + for (const auto &ev: timing.events) { + string.append(fmt::format("{:#x} {:#x} {}\n", ev.id(), ev.param(), ev.time)); + } + ImGui::SetClipboardText(string.c_str()); + } +} + +void showTimingSchedule(Timing &timing) { + using enum InjectState; + static std::size_t current = 0; + static uint64_t time_offset = 0; + static InjectState injectState = STOPPED; + if (ImGui::CollapsingHeader("SchedULe to inject", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SetNextItemWidth(80.f); + static uint64_t default_offset = 100000000UL; // 100ms + sliderScaled("[ms] default event offset", default_offset); + ImGui::SameLine(); + if (ImGui::Button("+")) { + timing.events.emplace_back(default_offset + (timing.events.empty() ? 0UL : timing.events.back().time)); + } + ImGui::SameLine(); + if (ImGui::Button("Clear##schedule")) { + timing.events.clear(); + } + ImGui::SameLine(); + saveLoadEvents(timing); + ImGui::SameLine(); + ImGui::Dummy({50, 5}); + startStopButtons(timing, current, time_offset, injectState); + // schedule table + static int freeze_cols = 1; + static int freeze_rows = 1; + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + ImVec2 outer_size{0.0f, TEXT_BASE_HEIGHT * 15}; + static ImGuiTableFlags flags = + ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + { + auto _ = ImScoped::Disabled(injectState != STOPPED); + if (auto _1 = ImScoped::Table("event schedule", static_cast(21 + timing.outputs.size()), flags, outer_size, 0.f)) { + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("time", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze() + ImGui::TableSetupColumn("bpcid"); + ImGui::TableSetupColumn("sid"); + ImGui::TableSetupColumn("bpid"); + ImGui::TableSetupColumn("gid"); + ImGui::TableSetupColumn("eventno"); + ImGui::TableSetupColumn("beam-in"); + ImGui::TableSetupColumn("bpc-start"); + ImGui::TableSetupColumn("req no beam"); + ImGui::TableSetupColumn("virt acc"); + ImGui::TableSetupColumn("bpcts"); + ImGui::TableSetupColumn("fid"); + ImGui::TableSetupColumn("id", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("param", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved1", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved2", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("reserved", ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("##inject", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("##remove", ImGuiTableColumnFlags_NoHide); + for (const auto & [_2, outputName, _3] : timing.outputs) { + auto [name, show] = outputConfig.contains(outputName) ? outputConfig.at(outputName) : std::pair{outputName, false}; + ImGui::TableSetupColumn(name.c_str(), show ? ImGuiTableColumnFlags_None : ImGuiTableColumnFlags_DefaultHide); + } + ImGui::TableSetupColumn("Trigger Delay", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Trigger Flat-Top", ImGuiTableColumnFlags_NoHide); + + ImGui::TableHeadersRow(); + + timing.events.erase(std::remove_if(timing.events.begin(), timing.events.end(), + [&timing, &default_offset_ = default_offset](auto &ev) { return drawScheduledEventTableRow(ev, timing, default_offset_); }), timing.events.end()); + } + } + } + scheduleUpcomingEvents(timing, current, time_offset, injectState); +} + +void outputsTable(const Timing &timing, float TEXT_BASE_HEIGHT) { + static int freeze_cols = 1; + static int freeze_rows = 1; + ImVec2 outer_size{(ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x) * 0.5f, TEXT_BASE_HEIGHT * 10}; + static ImGuiTableFlags flags = + ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + if (auto _ = ImScoped::Table("ioConfiguration", 8, flags, outer_size, 0.f)) { + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Direction"); + ImGui::TableSetupColumn("Output Enable"); + ImGui::TableSetupColumn("Input Termination"); + ImGui::TableSetupColumn("Special Out"); + ImGui::TableSetupColumn("Special In"); + ImGui::TableSetupColumn("Resolution"); + ImGui::TableSetupColumn("Level"); + ImGui::TableHeadersRow(); + + auto outputs = timing.receiver->getOutputs(); + + for (const auto & [name, port]:outputs) { + ImGui::PushID(&name); + ImGui::TableNextRow(); + auto port_proxy = saftlib::Output_Proxy::create(port); + auto input_name = port_proxy->getInput(); + auto input_proxy = input_name.empty() ? std::shared_ptr{} : saftlib::Input_Proxy::create(input_name); + tableColumnString("{}", name); + tableColumnString(""); //{}", port_proxy->direction); + tableColumnString("{}", port_proxy->getOutputEnable()); + tableColumnString("{}", input_proxy && input_proxy->getInputTermination()); + tableColumnString("{}", input_proxy && port_proxy->getSpecialPurposeOut()); + tableColumnString("{}", input_proxy && input_proxy->getSpecialPurposeIn()); + tableColumnString("{}", input_proxy && input_proxy->getResolution()); + tableColumnString("{}", port_proxy->getLogicLevelOut()); + ImGui::PopID(); + } + } +} + +void conditionsTable(const Timing &timing, const float TEXT_BASE_HEIGHT) { + static int freeze_cols_conds = 1; + static int freeze_rows_conds = 1; + ImVec2 outer_size_conds{0.0f, TEXT_BASE_HEIGHT * 10}; + static ImGuiTableFlags flags_conds = + ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + if (auto _ = ImScoped::Table("ioConditions", 10, flags_conds, outer_size_conds, 0.f)) { + ImGui::TableSetupScrollFreeze(freeze_cols_conds, freeze_rows_conds); + ImGui::TableSetupColumn("IO", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Offset"); + ImGui::TableSetupColumn("ID Filter"); + ImGui::TableSetupColumn("Mask"); + ImGui::TableSetupColumn("Accept Confligt"); + ImGui::TableSetupColumn("Accept Early"); + ImGui::TableSetupColumn("Accept Delayed"); + ImGui::TableSetupColumn("Accept Late"); + ImGui::TableSetupColumn("Active"); + ImGui::TableSetupColumn("OutputState"); + ImGui::TableHeadersRow(); + + for (const auto &[name, port] : timing.receiver->getOutputs()) { + auto port_proxy = saftlib::Output_Proxy::create(port); + for (const auto & condition : port_proxy->getAllConditions()) { + auto condition_proxy = saftlib::OutputCondition_Proxy::create(condition); + ImGui::PushID(&condition); + ImGui::TableNextRow(); + tableColumnString("{}", name); + tableColumnString("{}", condition_proxy->getOffset()); + tableColumnString("{:#016x}", condition_proxy->getID()); + tableColumnString("{:#016x}", condition_proxy->getMask()); + tableColumnString("{}", condition_proxy->getAcceptConflict()); + tableColumnString("{}", condition_proxy->getAcceptEarly()); + tableColumnString("{}", condition_proxy->getAcceptDelayed()); + tableColumnString("{}", condition_proxy->getAcceptLate()); + tableColumnString("{}", condition_proxy->getActive()); + tableColumnString("{}", condition_proxy->getOn()); + ImGui::PopID(); + } + } + } +} + +void showTRConfig(Timing &timing, bool &imGuiDemo, bool &imPlotDemo) { + ImGui::SetNextItemOpen(false, ImGuiCond_Once); + if (ImGui::CollapsingHeader("Timing Receiver IO configuration", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("ShowImGuiDemo", &imGuiDemo); + ImGui::SameLine(); + ImGui::Checkbox("ShowImPlotDemo", &imPlotDemo); + + if (!timing.initialized) { + ImGui::TextUnformatted("No timing receiver found"); + return; + } + if (timing.simulate) { + ImGui::TextUnformatted("Mocking the timing card by directly forwarding injected events"); + return; + } + uint64_t trTime = timing.currentTimeTAI(); + ImGui::TextUnformatted(fmt::format("{} -- ({} ns)\nTemperature: {}°C,\nGateware: {},\n(\"version\", \"{}\")", + trTime, taiNsToUtc(trTime), + timing.receiver->CurrentTemperature(), + fmt::join(timing.receiver->getGatewareInfo(), ",\n"), + timing.receiver->getGatewareVersion()).c_str()); + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + outputsTable(timing, TEXT_BASE_HEIGHT); + ImGui::SameLine(); + conditionsTable(timing, TEXT_BASE_HEIGHT); + } +} + +template +class TimePlot { +public: + static constexpr int bufferSize = 5000; + using Reader = decltype(std::declval().new_reader()); +private: + uint64_t startTime = 0; + FairPlot::ScrollingBuffer beamin{}; + FairPlot::ScrollingBuffer bpcids{}; + FairPlot::ScrollingBuffer sids{}; + FairPlot::ScrollingBuffer bpids{}; + FairPlot::ScrollingBuffer events{}; + Reader snoopReader; + int boolColormap = FairPlot::boolColormap(); + int bpcidColormap = FairPlot::bpcidColormap(); + int bpidColormap = FairPlot::bpidColormap(); + int sidColormap = FairPlot::sidColormap(); + std::optional previousContextEvent{}; + bool previousBPToggle = false; + bool previousSIDToggle = false; +public: + explicit TimePlot(BufferT &_events) : snoopReader{_events.new_reader()} { } + + void updateStreaming() { + auto newEvents = snoopReader.get(); + if (startTime == 0 && !newEvents.empty()) { + startTime = newEvents[0].time; + } + for (const auto &event: newEvents) { + double eventtime = (static_cast(event.time - startTime)) * 1e-9; + // filter out starts of new contexts + if (!previousContextEvent || (event.eventNo == 256 && (event.bpcid != previousContextEvent->bpcid || event.sid != previousContextEvent->sid || event.bpid != previousContextEvent->bpid))) { + previousSIDToggle = (previousContextEvent->sid != event.sid) ? !previousSIDToggle : previousSIDToggle; + previousBPToggle = (previousContextEvent->bpid != event.bpid) ? !previousBPToggle : previousBPToggle; + beamin.pushBack({eventtime, static_cast(event.flagBeamin)}); + bpcids.pushBack({eventtime, static_cast(getStableBPCIDColorIndex(static_cast(event.bpcid)))}); + sids.pushBack({eventtime, static_cast(previousSIDToggle)}); + bpids.pushBack({eventtime, static_cast(previousBPToggle)}); + previousContextEvent = event; + } + if (event.eventNo != 256) { // filter out non-BP_START events + events.pushBack({eventtime, static_cast(event.eventNo)}); + } + } + std::ignore = snoopReader.consume(newEvents.size()); // consume processed events + } + + void display(Timing &timing) const { + double plot_depth = 10; // [s] + auto currentTime = timing.currentTimeTAI(); + double time = (static_cast(currentTime - startTime)) * 1e-9; + if (ImGui::CollapsingHeader("Plot", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImPlot::BeginPlot("timing markers", ImVec2(-1,0), ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(fmt::format("t [s] + {}", taiNsToUtc(currentTime)).c_str(), nullptr, 0, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxisLimits(ImAxis_X1, -plot_depth, 0, ImGuiCond_Always); + + // plot freestanding events + if (!events.empty()) { + FairPlot::plotNamedEvents("Events", events, static_cast(0), -time); + } + + ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, 16.0f); + + if (!beamin.empty()) { + ImPlot::PushColormap(boolColormap); + FairPlot::plotStatusBar("beamin_plot", beamin, -time); + ImPlot::PopColormap(); + } + + if (!bpcids.empty()) { + ImPlot::PushColormap(bpcidColormap); + FairPlot::plotStatusBar("bpcid_plot", bpcids, -time); + ImPlot::PopColormap(); + } + if (!sids.empty()) { + ImPlot::PushColormap(sidColormap); + FairPlot::plotStatusBar("sid_plot", sids, -time); + ImPlot::PopColormap(); + } + if (!bpids.empty()) { + ImPlot::PushColormap(bpidColormap); + FairPlot::plotStatusBar("bpid_plot", bpids, -time); + ImPlot::PopColormap(); + } + ImPlot::PopStyleVar(); + ImPlot::EndPlot(); + } + } + } +}; + +std::pair openSDLWindow() { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) { + printf("Error: %s\n", SDL_GetError()); + return {}; + } + // Decide GL+GLSL versions +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + // Create window with graphics context + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + auto window_flags = static_cast(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + return {window, gl_context}; +} + +void processSDLEvents(SDL_Window * window, bool &done) { + for(SDL_Event event; SDL_PollEvent(&event); ) { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT || (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && + event.window.windowID == SDL_GetWindowID(window))) { + done = true; + } + } +} + +void startImGuiFrame() { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); +} + +void RenderToSDL(const ImGuiIO& io, SDL_Window *window) { + ImGui::Render(); + static ImVec4 clear_color{0.45f, 0.55f, 0.60f, 1.00f}; + glViewport(0, 0, static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y)); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); +} + +void shutdownSDL(SDL_Window *window, SDL_GLContext gl_context) { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); +} + +int showUI(Timing &timing) { + timing.initialize(); + auto [window, gl_context] = openSDLWindow(); + if (!window) return 200; + + const ImGuiIO& io = ImGui::GetIO(); (void)io; + + ImGui::StyleColorsLight(); // set light color scheme, alt: ImGui::StyleColorsDark() ImGui::StyleColorsClassic(); + app_header::loadHeaderFont(13.f); // default font for the application + auto headerFont = app_header::loadHeaderFont(32.f); + + bool imGuiDemo = false; + bool imPlotDemo = false; + bool done = false; + while (!done) { // main ui loop + timing.process(); + processSDLEvents(window, done); + startImGuiFrame(); + // create imgui "window" filling the whole platform window in the background s.t. other imgui windows may be put in front of it + static ImGuiWindowFlags imGuiWindowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBringToFrontOnFocus; + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->WorkPos); + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize); + if (auto _ = ImScoped::Window("Example: Fullscreen window", nullptr, imGuiWindowFlags)) { + app_header::draw_header_bar("Digitizer Timing Debug", headerFont); + showTimingEventTable(timing); + showTimingSchedule(timing); + static TimePlot plot{timing.snooped}; + plot.updateStreaming(); + plot.display(timing); + showTRConfig(timing, imGuiDemo, imPlotDemo); + } + if (imGuiDemo){ + ImGui::ShowDemoWindow(&imGuiDemo); + } + if (imPlotDemo) { + ImPlot::ShowDemoWindow(&imPlotDemo); + } + RenderToSDL(io, window); + } // main ui loop + shutdownSDL(window, gl_context); + return 0; +} + +int main() { + Timing timing; // an interface to the timing card allowing condition & io configuration and event injection & snooping + return showUI(timing); +} diff --git a/blocklib/timing/test/CMakeLists.txt b/blocklib/timing/test/CMakeLists.txt new file mode 100644 index 00000000..f1e44bef --- /dev/null +++ b/blocklib/timing/test/CMakeLists.txt @@ -0,0 +1,17 @@ +function(add_ut_test TEST_NAME) + add_executable(${TEST_NAME} ${TEST_NAME}.cpp) + if ((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")) # needed for clang15 (false positives, fixed in clang16) + target_compile_options(${TEST_NAME} PRIVATE -Wall) + target_link_options(${TEST_NAME} PRIVATE -Wall) + else () + target_compile_options(${TEST_NAME} PRIVATE -fsanitize=address -Wall) + target_link_options(${TEST_NAME} PRIVATE -fsanitize=address -Wall) + endif () + target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_BINARY_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(${TEST_NAME} PRIVATE gr-digitizers fmt ut) + add_test(NAME ${TEST_NAME} COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}) +endfunction() + +add_ut_test(qa_timing) +target_link_libraries(qa_timing PRIVATE timing PkgConfig::saftlib PkgConfig::etherbone) + diff --git a/blocklib/timing/test/qa_timing.cpp b/blocklib/timing/test/qa_timing.cpp new file mode 100644 index 00000000..318ca63d --- /dev/null +++ b/blocklib/timing/test/qa_timing.cpp @@ -0,0 +1,78 @@ +#include +#include + +using namespace std::string_literals; + +namespace fair::timing::test { + +const boost::ut::suite TimingTests = [] { + using namespace boost::ut; + using namespace std::chrono_literals; + + tag("timing-hardware") / + "snoopAndPublish"_test = [] { + Timing timing; + auto reader = timing.snooped.new_reader(); + timing.initialize(); + timing.injectEvent(Timing::Event{2000, 0x10, 0x20}, timing.currentTimeTAI()); + timing.injectEvent(Timing::Event{5000, 0x1234, 0x12}, timing.currentTimeTAI()); + std::this_thread::sleep_for(20ms); + timing.process(); + auto data = reader.get(); + expect(data.size() == 2_ul); + expect(data[0].id() == 0x10); + expect(data[1].param() == 0x12); + }; + + "snoopAndPublishSimulated"_test = [] { + Timing timing; + timing.simulate = true; + auto reader = timing.snooped.new_reader(); + timing.initialize(); + timing.injectEvent(Timing::Event{2000, 0x10, 0x20}, timing.currentTimeTAI()); + timing.injectEvent(Timing::Event{5000, 0x1234, 0x12}, timing.currentTimeTAI()); + timing.process(); + auto data = reader.get(); + expect(data.size() == 2_ul); + expect(data[0].id() == 0x10); + expect(data[1].param() == 0x12); + }; + + "TimingEvent"_test = [] { + Timing::Event defaultInitialized{}; + expect(defaultInitialized.fid == 1_ul); + expect(defaultInitialized.time == 0_ul); + }; + + "TimingEventFromString"_test = [] { + auto noEvent = Timing::Event::fromString(""); + expect(that % !noEvent.has_value()); + auto event = Timing::Event::fromString("0x1 0x123456789 123456789"); + expect(that % event.has_value()); + expect(that % event->time == 123456789); + expect(that % event->id() == 0x1); + expect(that % event->param() == 0x123456789); + auto realEvent = Timing::Event::fromString("0x112c100c00300085 0x5c00075bcd15 100000000"); + expect(that % realEvent.has_value()); + expect(that % realEvent->time == 100000000); + expect(that % realEvent->id() == 0x112c100c00300085); + expect(that % realEvent->param() == 0x5c00075bcd15); + expect(that % realEvent->bpcid == 23); + expect(that % realEvent->sid == 3); + expect(that % realEvent->bpid == 2); + expect(that % realEvent->gid == 300); + expect(that % realEvent->eventNo == 256); + expect(that % realEvent->flagBeamin); + expect(that % realEvent->flagBpcStart); + expect(that % !realEvent->reqNoBeam); + expect(that % realEvent->virtAcc == 5); + expect(that % realEvent->fid == 1); + expect(that % realEvent->bpcts == 123456789); + }; +}; + +} + +int +main() { /* tests are statically executed */ +} diff --git a/cmake/CMakeRC.cmake b/cmake/CMakeRC.cmake new file mode 100644 index 00000000..dc40d0e8 --- /dev/null +++ b/cmake/CMakeRC.cmake @@ -0,0 +1,644 @@ +# This block is executed when generating an intermediate resource file, not when +# running in CMake configure mode +if(_CMRC_GENERATE_MODE) + # Read in the digits + file(READ "${INPUT_FILE}" bytes HEX) + # Format each pair into a character literal. Heuristics seem to favor doing + # the conversion in groups of five for fastest conversion + string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") + # Since we did this in groups, we have some leftovers to clean up + string(LENGTH "${bytes}" n_bytes2) + math(EXPR n_bytes "${n_bytes2} / 2") + math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above + set(cleanup_re "$") + set(cleanup_sub ) + while(remainder) + set(cleanup_re "(..)${cleanup_re}") + set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") + math(EXPR remainder "${remainder} - 1") + endwhile() + if(NOT cleanup_re STREQUAL "$") + string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") + endif() + string(CONFIGURE [[ + namespace { const char file_array[] = { @chars@ 0 }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + @n_bytes@; + }}} + ]] code) + file(WRITE "${OUTPUT_FILE}" "${code}") + # Exit from the script. Nothing else needs to be processed + return() +endif() + +set(_version 2.0.0) + +cmake_minimum_required(VERSION 3.3...3.16) +include(CMakeParseArguments) + +if(COMMAND cmrc_add_resource_library) + if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) + message(WARNING "More than one CMakeRC version has been included in this project.") + endif() + # CMakeRC has already been included! Don't do anything + return() +endif() + +set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") + +set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") + +function(_cmrc_normalize_path var) + set(path "${${var}}") + file(TO_CMAKE_PATH "${path}" path) + while(path MATCHES "//") + string(REPLACE "//" "/" path "${path}") + endwhile() + string(REGEX REPLACE "/+$" "" path "${path}") + set("${var}" "${path}" PARENT_SCOPE) +endfunction() + +get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) +set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") +# Let's generate the primary include file +file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") +set(hpp_content [==[ +#ifndef CMRC_CMRC_HPP_INCLUDED +#define CMRC_CMRC_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) +#define CMRC_NO_EXCEPTIONS 1 +#endif + +namespace cmrc { namespace detail { struct dummy; } } + +#define CMRC_DECLARE(libid) \ + namespace cmrc { namespace detail { \ + struct dummy; \ + static_assert(std::is_same::value, "CMRC_DECLARE() must only appear at the global namespace"); \ + } } \ + namespace cmrc { namespace libid { \ + cmrc::embedded_filesystem get_filesystem(); \ + } } static_assert(true, "") + +namespace cmrc { + +class file { + const char* _begin = nullptr; + const char* _end = nullptr; + +public: + using iterator = const char*; + using const_iterator = iterator; + iterator begin() const noexcept { return _begin; } + iterator cbegin() const noexcept { return _begin; } + iterator end() const noexcept { return _end; } + iterator cend() const noexcept { return _end; } + std::size_t size() const { return static_cast(std::distance(begin(), end())); } + + file() = default; + file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} +}; + +class directory_entry; + +namespace detail { + +class directory; +class file_data; + +class file_or_directory { + union _data_t { + class file_data* file_data; + class directory* directory; + } _data; + bool _is_file = true; + +public: + explicit file_or_directory(file_data& f) { + _data.file_data = &f; + } + explicit file_or_directory(directory& d) { + _data.directory = &d; + _is_file = false; + } + bool is_file() const noexcept { + return _is_file; + } + bool is_directory() const noexcept { + return !is_file(); + } + const directory& as_directory() const noexcept { + assert(!is_file()); + return *_data.directory; + } + const file_data& as_file() const noexcept { + assert(is_file()); + return *_data.file_data; + } +}; + +class file_data { +public: + const char* begin_ptr; + const char* end_ptr; + file_data(const file_data&) = delete; + file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} +}; + +inline std::pair split_path(const std::string& path) { + auto first_sep = path.find("/"); + if (first_sep == path.npos) { + return std::make_pair(path, ""); + } else { + return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); + } +} + +struct created_subdirectory { + class directory& directory; + class file_or_directory& index_entry; +}; + +class directory { + std::list _files; + std::list _dirs; + std::map _index; + + using base_iterator = std::map::const_iterator; + +public: + + directory() = default; + directory(const directory&) = delete; + + created_subdirectory add_subdir(std::string name) & { + _dirs.emplace_back(); + auto& back = _dirs.back(); + auto& fod = _index.emplace(name, file_or_directory{back}).first->second; + return created_subdirectory{back, fod}; + } + + file_or_directory* add_file(std::string name, const char* begin, const char* end) & { + assert(_index.find(name) == _index.end()); + _files.emplace_back(begin, end); + return &_index.emplace(name, file_or_directory{_files.back()}).first->second; + } + + const file_or_directory* get(const std::string& path) const { + auto pair = split_path(path); + auto child = _index.find(pair.first); + if (child == _index.end()) { + return nullptr; + } + auto& entry = child->second; + if (pair.second.empty()) { + // We're at the end of the path + return &entry; + } + + if (entry.is_file()) { + // We can't traverse into a file. Stop. + return nullptr; + } + // Keep going down + return entry.as_directory().get(pair.second); + } + + class iterator { + base_iterator _base_iter; + base_iterator _end_iter; + public: + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::input_iterator_tag; + + iterator() = default; + explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} + + iterator begin() const noexcept { + return *this; + } + + iterator end() const noexcept { + return iterator(_end_iter, _end_iter); + } + + inline value_type operator*() const noexcept; + + bool operator==(const iterator& rhs) const noexcept { + return _base_iter == rhs._base_iter; + } + + bool operator!=(const iterator& rhs) const noexcept { + return !(*this == rhs); + } + + iterator operator++() noexcept { + auto cp = *this; + ++_base_iter; + return cp; + } + + iterator& operator++(int) noexcept { + ++_base_iter; + return *this; + } + }; + + using const_iterator = iterator; + + iterator begin() const noexcept { + return iterator(_index.begin(), _index.end()); + } + + iterator end() const noexcept { + return iterator(); + } +}; + +inline std::string normalize_path(std::string path) { + while (path.find("/") == 0) { + path.erase(path.begin()); + } + while (!path.empty() && (path.rfind("/") == path.size() - 1)) { + path.pop_back(); + } + auto off = path.npos; + while ((off = path.find("//")) != path.npos) { + path.erase(path.begin() + static_cast(off)); + } + return path; +} + +using index_type = std::map; + +} // detail + +class directory_entry { + std::string _fname; + const detail::file_or_directory* _item; + +public: + directory_entry() = delete; + explicit directory_entry(std::string filename, const detail::file_or_directory& item) + : _fname(filename) + , _item(&item) + {} + + const std::string& filename() const & { + return _fname; + } + std::string filename() const && { + return std::move(_fname); + } + + bool is_file() const { + return _item->is_file(); + } + + bool is_directory() const { + return _item->is_directory(); + } +}; + +directory_entry detail::directory::iterator::operator*() const noexcept { + assert(begin() != end()); + return directory_entry(_base_iter->first, _base_iter->second); +} + +using directory_iterator = detail::directory::iterator; + +class embedded_filesystem { + // Never-null: + const cmrc::detail::index_type* _index; + const detail::file_or_directory* _get(std::string path) const { + path = detail::normalize_path(path); + auto found = _index->find(path); + if (found == _index->end()) { + return nullptr; + } else { + return found->second; + } + } + +public: + explicit embedded_filesystem(const detail::index_type& index) + : _index(&index) + {} + + file open(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr || !entry_ptr->is_file()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + auto& dat = entry_ptr->as_file(); + return file{dat.begin_ptr, dat.end_ptr}; + } + + bool is_file(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_file(); + } + + bool is_directory(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_directory(); + } + + bool exists(const std::string& path) const noexcept { + return !!_get(path); + } + + directory_iterator iterate_directory(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + if (!entry_ptr->is_directory()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error not a directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::not_a_directory), path); +#endif + } + return entry_ptr->as_directory().begin(); + } +}; + +} + +#endif // CMRC_CMRC_HPP_INCLUDED +]==]) + +set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") +set(_generate 1) +if(EXISTS "${cmrc_hpp}") + file(READ "${cmrc_hpp}" _current) + if(_current STREQUAL hpp_content) + set(_generate 0) + endif() +endif() +file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) + +add_library(cmrc-base INTERFACE) +target_include_directories(cmrc-base INTERFACE $) +# Signal a basic C++11 feature to require C++11. +target_compile_features(cmrc-base INTERFACE cxx_nullptr) +set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) +add_library(cmrc::base ALIAS cmrc-base) + +function(cmrc_add_resource_library name) + set(args ALIAS NAMESPACE TYPE) + cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") + # Generate the identifier for the resource library's namespace + set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") + if(NOT DEFINED ARG_NAMESPACE) + # Check that the library name is also a valid namespace + if(NOT name MATCHES "${ns_re}") + message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") + endif() + set(ARG_NAMESPACE "${name}") + else() + if(NOT ARG_NAMESPACE MATCHES "${ns_re}") + message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") + endif() + endif() + set(libname "${name}") + # Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if + # not set + if(NOT DEFINED ARG_TYPE) + set(ARG_TYPE STATIC) + elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$") + message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)") + set(ARG_TYPE STATIC) + endif() + # Generate a library with the compiled in character arrays. + string(CONFIGURE [=[ + #include + #include + #include + + namespace cmrc { + namespace @ARG_NAMESPACE@ { + + namespace res_chars { + // These are the files which are available in this resource library + $, + > + } + + namespace { + + const cmrc::detail::index_type& + get_root_index() { + static cmrc::detail::directory root_directory_; + static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; + static cmrc::detail::index_type root_index; + root_index.emplace("", &root_directory_fod); + struct dir_inl { + class cmrc::detail::directory& directory; + }; + dir_inl root_directory_dir{root_directory_}; + (void)root_directory_dir; + $, + > + $, + > + return root_index; + } + + } + + cmrc::embedded_filesystem get_filesystem() { + static auto& index = get_root_index(); + return cmrc::embedded_filesystem{index}; + } + + } // @ARG_NAMESPACE@ + } // cmrc + ]=] cpp_content @ONLY) + get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) + get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) + string(REPLACE "\n " "\n" cpp_content "${cpp_content}") + file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") + get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) + add_custom_command(OUTPUT "${libcpp}" + DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" + COMMENT "Generating ${name} resource loader" + ) + # Generate the actual static library. Each source file is just a single file + # with a character array compiled in containing the contents of the + # corresponding resource file. + add_library(${name} ${ARG_TYPE} ${libcpp}) + set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") + set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") + target_link_libraries(${name} PUBLIC cmrc::base) + set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) + if(ARG_ALIAS) + add_library("${ARG_ALIAS}" ALIAS ${name}) + endif() + cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) +endfunction() + +function(_cmrc_register_dirs name dirpath) + if(dirpath STREQUAL "") + return() + endif() + # Skip this dir if we have already registered it + get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) + if(dirpath IN_LIST registered) + return() + endif() + # Register the parent directory first + get_filename_component(parent "${dirpath}" DIRECTORY) + if(NOT parent STREQUAL "") + _cmrc_register_dirs("${name}" "${parent}") + endif() + # Now generate the registration + set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") + _cm_encode_fpath(sym "${dirpath}") + if(parent STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${parent}") + endif() + get_filename_component(leaf "${dirpath}" NAME) + set_property( + TARGET "${name}" + APPEND PROPERTY CMRC_MAKE_DIRS + "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" + "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" + ) +endfunction() + +function(cmrc_add_resources name) + get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) + if(NOT TARGET ${name} OR NOT is_reslib) + message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") + return() + endif() + + set(options) + set(args WHENCE PREFIX) + set(list_args) + cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") + + if(NOT ARG_WHENCE) + set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + _cmrc_normalize_path(ARG_WHENCE) + get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) + + # Generate the identifier for the resource library's namespace + get_target_property(lib_ns "${name}" CMRC_NAMESPACE) + + get_target_property(libdir ${name} CMRC_LIBDIR) + get_target_property(target_dir ${name} SOURCE_DIR) + file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") + if(reldir MATCHES "^\\.\\.") + message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") + return() + endif() + + foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) + _cmrc_normalize_path(input) + get_filename_component(abs_in "${input}" ABSOLUTE) + # Generate a filename based on the input filename that we can put in + # the intermediate directory. + file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") + if(relpath MATCHES "^\\.\\.") + # For now we just error on files that exist outside of the soure dir. + message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") + continue() + endif() + if(DEFINED ARG_PREFIX) + _cmrc_normalize_path(ARG_PREFIX) + endif() + if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") + set(ARG_PREFIX "${ARG_PREFIX}/") + endif() + get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) + _cmrc_register_dirs("${name}" "${dirpath}") + get_filename_component(abs_out "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE) + # Generate a symbol name relpath the file's character array + _cm_encode_fpath(sym "${relpath}") + # Get the symbol name for the parent directory + if(dirpath STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${dirpath}") + endif() + # Generate the rule for the intermediate source file + _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") + target_sources(${name} PRIVATE "${abs_out}") + set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS + "// Pointers to ${input}" + "extern const char* const ${sym}_begin\;" + "extern const char* const ${sym}_end\;" + ) + get_filename_component(leaf "${relpath}" NAME) + set_property( + TARGET ${name} + APPEND PROPERTY CMRC_MAKE_FILES + "root_index.emplace(" + " \"${ARG_PREFIX}${relpath}\"," + " ${parent_sym}_dir.directory.add_file(" + " \"${leaf}\"," + " res_chars::${sym}_begin," + " res_chars::${sym}_end" + " )" + ")\;" + ) + endforeach() +endfunction() + +function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) + add_custom_command( + # This is the file we will generate + OUTPUT "${outfile}" + # These are the primary files that affect the output + DEPENDS "${infile}" "${_CMRC_SCRIPT}" + COMMAND + "${CMAKE_COMMAND}" + -D_CMRC_GENERATE_MODE=TRUE + -DNAMESPACE=${lib_ns} + -DSYMBOL=${symbol} + "-DINPUT_FILE=${infile}" + "-DOUTPUT_FILE=${outfile}" + -P "${_CMRC_SCRIPT}" + COMMENT "Generating intermediate file for ${infile}" + ) +endfunction() + +function(_cm_encode_fpath var fpath) + string(MAKE_C_IDENTIFIER "${fpath}" ident) + string(MD5 hash "${fpath}") + string(SUBSTRING "${hash}" 0 4 hash) + set(${var} f_${hash}_${ident} PARENT_SCOPE) +endfunction() diff --git a/sonar-project.properties b/sonar-project.properties index da0523a7..500f31c5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -14,4 +14,4 @@ sonar.cfamily.cpp23.enabled=true # exclude benchmark which crashes the Analysis #sonar.exclusions=bench/bm_case1.cpp -#sonar.coverageReportPaths=/home/runner/work/graph-prototype/build/coverage.xml +sonar.coverageReportPaths=/home/runner/work/gr-digitizers/build/coverage.xml