From 23aa9c2814de02411c5e9b2b1696f5ef9927794e Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Tue, 11 Mar 2025 12:07:10 +0100 Subject: [PATCH 001/103] added reorder variable, changed function ArrangeDBitData to support reordering and no reordering. Moved transceiver data such that it is contiguous with rearranged digital data --- slsReceiverSoftware/src/DataProcessor.cpp | 128 ++++++++++++++++------ slsReceiverSoftware/src/DataProcessor.h | 8 +- 2 files changed, 99 insertions(+), 37 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index fd5f2456a..49a5ed6f3 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -75,6 +75,10 @@ void DataProcessor::SetCtbDbitList(std::vector value) { ctbDbitList = value; } +void DataProcessor::SetReorder(const bool value) { + reorder = value; +} + void DataProcessor::SetCtbDbitOffset(int value) { ctbDbitOffset = value; } void DataProcessor::SetQuadEnable(bool value) { quadEnable = value; } @@ -213,8 +217,9 @@ std::string DataProcessor::CreateVirtualFile( "Skipping virtual hdf5 file since rx_roi is enabled."); } - bool gotthard25um = - (generalData->detType == GOTTHARD2 && (numModX * numModY) == 2); + bool gotthard25um = ((generalData->detType == GOTTHARD || + generalData->detType == GOTTHARD2) && + (numModX * numModY) == 2); // 0 for infinite files uint32_t framesPerFile = @@ -357,7 +362,7 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, // rearrange ctb digital bits (if ctbDbitlist is not empty) if (!ctbDbitList.empty()) { - RearrangeDbitData(size, data); + ArrangeDbitData(size, data); } // 'stream Image' check has to be done here before crop image @@ -504,6 +509,16 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { // missing packet switch (generalData->detType) { + // for gotthard, 1st packet: 4 bytes fnum, CACA + CACA, 639*2 bytes + // data + // 2nd packet: 4 bytes fnum, previous 1*2 bytes data + + // 640*2 bytes data !! + case GOTTHARD: + if (pnum == 0u) + memset(data + (pnum * dsize), 0xFF, dsize - 2); + else + memset(data + (pnum * dsize), 0xFF, dsize + 2); + break; case CHIPTESTBOARD: case XILINX_CHIPTESTBOARD: if (pnum == (pperFrame - 1)) @@ -520,10 +535,10 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { } /** ctb specific */ -void DataProcessor::RearrangeDbitData(size_t &size, char *data) { - int nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); - int nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); - int nTransceiverDataBytes = generalData->GetNumberOfTransceiverDatabytes(); +void DataProcessor::ArrangeDbitData(size_t &size, char *data) { + size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); + size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); + size_t nTransceiverDataBytes = generalData->GetNumberOfTransceiverDatabytes(); // TODO! (Erik) Refactor and add tests int ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; @@ -534,47 +549,90 @@ void DataProcessor::RearrangeDbitData(size_t &size, char *data) { return; } - const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); - - // const int numResult8Bits = ceil((numDigitalSamples * ctbDbitList.size()) - // / 8.00); - int numBitsPerDbit = numDigitalSamples; - if ((numBitsPerDbit % 8) != 0) - numBitsPerDbit += (8 - (numDigitalSamples % 8)); - const int totalNumBytes = (numBitsPerDbit / 8) * ctbDbitList.size(); - std::vector result(totalNumBytes); - uint8_t *dest = &result[0]; - auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); - // loop through digital bit enable vector - int bitoffset = 0; - for (auto bi : ctbDbitList) { - // where numbits * numDigitalSamples is not a multiple of 8 - if (bitoffset != 0) { - bitoffset = 0; - ++dest; - } + const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); - // loop through the frame digital data - for (auto *ptr = source; ptr < (source + numDigitalSamples);) { - // get selected bit from each 8 bit - uint8_t bit = (*ptr++ >> bi) & 1; - *dest |= bit << bitoffset; - ++bitoffset; - // extract destination in 8 bit batches - if (bitoffset == 8) { + int totalNumBytes = 0; //number of bytes for selected digital data given by dtbDbitList + + //store each selected bit from all samples consecutively + if(reorder) { + int numBitsPerDbit = numDigitalSamples; //num bits per selected digital Bit for all samples + if ((numBitsPerDbit % 8) != 0) + numBitsPerDbit += (8 - (numDigitalSamples % 8)); + totalNumBytes = (numBitsPerDbit / 8) * ctbDbitList.size(); + } + //store all selected bits from one sample consecutively + else { + size_t numBitsPerSample = ctbDbitList.size(); //num bits for all selected bits per sample + if ((numBitsPerSample % 8) != 0) + numBitsPerSample += (8 - (numBitsPerSample % 8)); + totalNumBytes = (numBitsPerSample / 8) * numDigitalSamples; + } + + std::vector result(totalNumBytes, 0); + uint8_t *dest = &result[0]; + + if(reorder) { + // loop through digital bit enable vector + int bitoffset = 0; + for (auto bi : ctbDbitList) { + // where numbits * numDigitalSamples is not a multiple of 8 + if (bitoffset != 0) { bitoffset = 0; ++dest; } + + // loop through the frame digital data + for (auto *ptr = source; ptr < (source + numDigitalSamples);) { + // get selected bit from each 8 bit + uint8_t bit = (*ptr++ >> bi) & 1; + *dest |= bit << bitoffset; + ++bitoffset; + // extract destination in 8 bit batches + if (bitoffset == 8) { + bitoffset = 0; + ++dest; + } + } + } + } + else { + // loop through the digital data + int bitoffset = 0; + for (auto *ptr = source; ptr < (source + numDigitalSamples); ++ptr) { + // where bit enable vector size is not a multiple of 8 + if (bitoffset != 0) { + bitoffset = 0; + ++dest; + } + + // loop through digital bit enable vector + for (auto bi : ctbDbitList) { + // get selected bit from each 64 bit + uint8_t bit = (*ptr >> bi) & 1; + *dest |= bit << bitoffset; + ++bitoffset; + // extract destination in 8 bit batches + if (bitoffset == 8) { + bitoffset = 0; + ++dest; + } + } } } // copy back to memory and update size memcpy(data + nAnalogDataBytes, result.data(), totalNumBytes * sizeof(uint8_t)); - size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + ctbDbitOffset + + + size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + nTransceiverDataBytes; + + //check if size changed, if so move transceiver data to avoid gap in memory + if(size < nAnalogDataBytes + nDigitalDataBytes + nTransceiverDataBytes) + memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), data + nAnalogDataBytes + nDigitalDataBytes, nTransceiverDataBytes); + LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes << " nAnalogDataBytes:" << nAnalogDataBytes << " ctbDbitOffset:" << ctbDbitOffset diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 29ea9e84c..4937d0d37 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -46,6 +46,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { void SetStreamingStartFnum(uint32_t value); void SetFramePadding(bool enable); void SetCtbDbitList(std::vector value); + void SetReorder(const bool reorder); void SetCtbDbitOffset(int value); void SetQuadEnable(bool value); void SetFlipRows(bool fd); @@ -139,9 +140,11 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { /** * Align corresponding digital bits together (CTB only if ctbDbitlist is not - * empty) + * empty) + * set variable reorder to true if, data should be rearranged such that + * individual digital bits from all samples are consecutive in memory */ - void RearrangeDbitData(size_t &size, char *data); + void ArrangeDbitData(size_t &size, char *data); void CropImage(size_t &size, char *data); @@ -165,6 +168,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { struct timespec timerbegin {}; bool framePadding; std::vector ctbDbitList; + bool reorder{false}; //true if data should be reordered TODO: add as mode int ctbDbitOffset; std::atomic startedFlag{false}; std::atomic firstIndex{0}; From 8d87a6ee4ecedc2da750ac7273b4b4727e949de3 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Tue, 11 Mar 2025 16:32:31 +0100 Subject: [PATCH 002/103] used clang-formating --- slsReceiverSoftware/src/DataProcessor.cpp | 43 ++++++++++++---------- slsReceiverSoftware/src/DataProcessor.h | 8 ++-- slsReceiverSoftware/src/Implementation.cpp | 6 +-- slsSupportLib/include/sls/versionAPI.h | 10 ++--- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 49a5ed6f3..e80ab531e 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -75,9 +75,7 @@ void DataProcessor::SetCtbDbitList(std::vector value) { ctbDbitList = value; } -void DataProcessor::SetReorder(const bool value) { - reorder = value; -} +void DataProcessor::SetReorder(const bool value) { reorder = value; } void DataProcessor::SetCtbDbitOffset(int value) { ctbDbitOffset = value; } @@ -536,9 +534,10 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { /** ctb specific */ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { - size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); + size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); - size_t nTransceiverDataBytes = generalData->GetNumberOfTransceiverDatabytes(); + size_t nTransceiverDataBytes = + generalData->GetNumberOfTransceiverDatabytes(); // TODO! (Erik) Refactor and add tests int ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; @@ -553,18 +552,21 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); - int totalNumBytes = 0; //number of bytes for selected digital data given by dtbDbitList + int totalNumBytes = + 0; // number of bytes for selected digital data given by dtbDbitList - //store each selected bit from all samples consecutively - if(reorder) { - int numBitsPerDbit = numDigitalSamples; //num bits per selected digital Bit for all samples + // store each selected bit from all samples consecutively + if (reorder) { + int numBitsPerDbit = numDigitalSamples; // num bits per selected digital + // Bit for all samples if ((numBitsPerDbit % 8) != 0) numBitsPerDbit += (8 - (numDigitalSamples % 8)); totalNumBytes = (numBitsPerDbit / 8) * ctbDbitList.size(); } - //store all selected bits from one sample consecutively - else { - size_t numBitsPerSample = ctbDbitList.size(); //num bits for all selected bits per sample + // store all selected bits from one sample consecutively + else { + size_t numBitsPerSample = + ctbDbitList.size(); // num bits for all selected bits per sample if ((numBitsPerSample % 8) != 0) numBitsPerSample += (8 - (numBitsPerSample % 8)); totalNumBytes = (numBitsPerSample / 8) * numDigitalSamples; @@ -573,7 +575,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { std::vector result(totalNumBytes, 0); uint8_t *dest = &result[0]; - if(reorder) { + if (reorder) { // loop through digital bit enable vector int bitoffset = 0; for (auto bi : ctbDbitList) { @@ -596,8 +598,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { } } } - } - else { + } else { // loop through the digital data int bitoffset = 0; for (auto *ptr = source; ptr < (source + numDigitalSamples); ++ptr) { @@ -625,13 +626,15 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { // copy back to memory and update size memcpy(data + nAnalogDataBytes, result.data(), totalNumBytes * sizeof(uint8_t)); - + size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + nTransceiverDataBytes; - - //check if size changed, if so move transceiver data to avoid gap in memory - if(size < nAnalogDataBytes + nDigitalDataBytes + nTransceiverDataBytes) - memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), data + nAnalogDataBytes + nDigitalDataBytes, nTransceiverDataBytes); + + // check if size changed, if so move transceiver data to avoid gap in memory + if (size < nAnalogDataBytes + nDigitalDataBytes + nTransceiverDataBytes) + memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), + data + nAnalogDataBytes + nDigitalDataBytes, + nTransceiverDataBytes); LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes << " nAnalogDataBytes:" << nAnalogDataBytes diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 4937d0d37..d09a9ca3f 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -140,11 +140,11 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { /** * Align corresponding digital bits together (CTB only if ctbDbitlist is not - * empty) - * set variable reorder to true if, data should be rearranged such that + * empty) + * set variable reorder to true if, data should be rearranged such that * individual digital bits from all samples are consecutive in memory */ - void ArrangeDbitData(size_t &size, char *data); + void ArrangeDbitData(size_t &size, char *data); void CropImage(size_t &size, char *data); @@ -168,7 +168,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { struct timespec timerbegin {}; bool framePadding; std::vector ctbDbitList; - bool reorder{false}; //true if data should be reordered TODO: add as mode + bool reorder{false}; // true if data should be reordered TODO: add as mode int ctbDbitOffset; std::atomic startedFlag{false}; std::atomic firstIndex{0}; diff --git a/slsReceiverSoftware/src/Implementation.cpp b/slsReceiverSoftware/src/Implementation.cpp index 6016d10fb..8104e8c7a 100644 --- a/slsReceiverSoftware/src/Implementation.cpp +++ b/slsReceiverSoftware/src/Implementation.cpp @@ -915,10 +915,10 @@ void Implementation::CreateUDPSockets() { } void Implementation::SetupWriter() { - + try { - //check if filePath empty and throw error - if(filePath.empty()){ + // check if filePath empty and throw error + if (filePath.empty()) { throw ReceiverError("File path cannot be empty"); } // check if folder exists and throw if it cant create diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index 4fbb0db7b..7a0e6c77b 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -3,10 +3,10 @@ /** API versions */ #define APILIB "developer 0x241122" #define APIRECEIVER "developer 0x241122" -#define APICTB "developer 0x250310" +#define APICTB "developer 0x250310" #define APIGOTTHARD2 "developer 0x250310" -#define APIJUNGFRAU "developer 0x250310" -#define APIMYTHEN3 "developer 0x250310" -#define APIMOENCH "developer 0x250310" +#define APIJUNGFRAU "developer 0x250310" +#define APIMYTHEN3 "developer 0x250310" +#define APIMOENCH "developer 0x250310" #define APIXILINXCTB "developer 0x250310" -#define APIEIGER "developer 0x250310" +#define APIEIGER "developer 0x250310" From 63bb79d727bd3998a696ce347d238d93a8009f8c Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 12 Mar 2025 10:51:18 +0100 Subject: [PATCH 003/103] added first rather generic test for Rearrange Function, could be changed to a parametrized test --- slsReceiverSoftware/src/DataProcessor.h | 17 +-- slsReceiverSoftware/tests/CMakeLists.txt | 3 + .../tests/test-ArrangeDataBasedOnBitList.cpp | 134 ++++++++++++++++++ 3 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index d09a9ca3f..458e750c5 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -92,6 +92,15 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { size_t &, void *), void *arg); + protected: + /** + * Align corresponding digital bits together (CTB only if ctbDbitlist is not + * empty) + * set variable reorder to true if, data should be rearranged such that + * individual digital bits from all samples are consecutive in memory + */ + void ArrangeDbitData(size_t &size, char *data); + private: void RecordFirstIndex(uint64_t fnum); @@ -138,14 +147,6 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { void PadMissingPackets(sls_receiver_header header, char *data); - /** - * Align corresponding digital bits together (CTB only if ctbDbitlist is not - * empty) - * set variable reorder to true if, data should be rearranged such that - * individual digital bits from all samples are consecutive in memory - */ - void ArrangeDbitData(size_t &size, char *data); - void CropImage(size_t &size, char *data); static const std::string typeName; diff --git a/slsReceiverSoftware/tests/CMakeLists.txt b/slsReceiverSoftware/tests/CMakeLists.txt index e656b51ad..9a9455408 100755 --- a/slsReceiverSoftware/tests/CMakeLists.txt +++ b/slsReceiverSoftware/tests/CMakeLists.txt @@ -3,6 +3,9 @@ target_sources(tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test-GeneralData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test-CircularFifo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test-ArrangeDataBasedOnBitList.cpp ) target_include_directories(tests PUBLIC "$") + +message(STATUS "Resolved path: ${CMAKE_CURRENT_SOURCE_DIR}/../src") \ No newline at end of file diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp new file mode 100644 index 000000000..3012ddf64 --- /dev/null +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: LGPL-3.0-or-other +// Copyright (C) 2025 Contributors to the SLS Detector Package +/************************************************ + * @file test-ArrangeDataBasedOnBitList.cpp + * @short test case for DataProcessor member function ArrangeDbitData, + ***********************************************/ + +#include "DataProcessor.h" +#include "GeneralData.h" +#include "catch.hpp" +#include + +namespace sls { + +// dummy GeneralData class for testing +class GeneralDataTest : public GeneralData { + + public: + int GetNumberOfAnalogDatabytes() { return nAnalogBytes; }; + + int GetNumberOfDigitalDatabytes() { return nDigitalBytes; }; + + int GetNumberOfTransceiverDatabytes() { return nTransceiverBytes; }; + + void SetNumberOfAnalogDatabytes(int value) { nAnalogBytes = value; } + + void SetNumberOfDigitalDatabytes(int value) { nDigitalBytes = value; } + + void SetNumberOfTransceiverDatabytes(int value) { + nTransceiverBytes = value; + } + + private: + int nAnalogBytes; + int nDigitalBytes; + int nTransceiverBytes; +}; + +// oke maybe just make it static +class DataProcessorTest : public DataProcessor { + public: + DataProcessorTest() : DataProcessor(0){}; + ~DataProcessorTest(){}; + void ArrangeDbitData(size_t &size, char *data) { + DataProcessor::ArrangeDbitData(size, data); + } +}; + +TEST_CASE("Arrange with reorder false") { + DataProcessorTest dataprocessor; + + std::vector bitlist{1, 4, 5}; + dataprocessor.SetCtbDbitList(bitlist); + + size_t num_digital_samples = 5; // size_t or uint8_t ? + size_t num_digital_bytes = num_digital_samples * 8; + size_t num_analog_bytes = 1; + size_t num_transceiver_bytes = 2; + size_t random_offset_bytes = 0; + + size_t size = num_analog_bytes + num_digital_bytes + num_transceiver_bytes + + random_offset_bytes; + + char *data = new char[size]; + + char dummy_value = static_cast(125); + memset(data, dummy_value, num_analog_bytes); + memset(data + num_analog_bytes, 0xFF, + num_digital_bytes); // all digital bits are one + memset(data + num_digital_bytes + num_analog_bytes, dummy_value, + num_transceiver_bytes); + + GeneralDataTest *generaldata = new GeneralDataTest; + generaldata->SetNumberOfAnalogDatabytes(num_analog_bytes); + generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes); + generaldata->SetNumberOfTransceiverDatabytes(num_transceiver_bytes); + + dataprocessor.SetGeneralData(generaldata); + + size_t new_num_digital_bytes = + (bitlist.size() / 8 + static_cast(bitlist.size() % 8 != 0)) * + num_digital_samples; + + size_t new_size = + num_analog_bytes + num_transceiver_bytes + new_num_digital_bytes; + + char *new_data = new char[new_size]; + + memset(new_data, dummy_value, num_analog_bytes); + + // TODO: Make test more explicit and less generic + size_t num_bytes_for_bitlist = + bitlist.size() / 8 + static_cast(bitlist.size() % 8 != 0); + + // 125 7 7 7 7 7 125 125 + for (size_t sample = 0; sample < num_digital_samples; ++sample) { + if (bitlist.size() / 8 != 0) { + memset(new_data + sample * num_bytes_for_bitlist + num_analog_bytes, + 0xFF, + bitlist.size() / 8); // set to 1 + } else if (bitlist.size() % 8 != 0) { + memset(new_data + sample * num_bytes_for_bitlist + + bitlist.size() / 8 + num_analog_bytes, + static_cast(pow(2, (bitlist.size() % 8)) - 1), + 1); // convert binary number to decimal + } + } + + memset(new_data + new_num_digital_bytes + num_analog_bytes, dummy_value, + num_transceiver_bytes); + + dataprocessor.ArrangeDbitData(size, data); + + CHECK(size == new_size); + + CHECK(memcmp(data, new_data, size) == 0); + + // Free allocated memory + delete[] data; + delete[] new_data; + delete generaldata; +} + +// TEST_CASE("Arrange with reorder on") { + +//} + +// test case reorder on and bitoffset set + +// test with different samples + +// test with sample number not divisable and ctbitlist not divisable + +} // namespace sls From a74fb2bcd1306a71f6e1e8ad4398fca01aa1f95f Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 12 Mar 2025 16:12:00 +0100 Subject: [PATCH 004/103] added function Reorder --- slsReceiverSoftware/src/DataProcessor.cpp | 88 ++++++++++++++++++++++- slsReceiverSoftware/src/DataProcessor.h | 6 ++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index e80ab531e..ce744e531 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -358,6 +358,10 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, if (framePadding && nump < generalData->packetsPerFrame) PadMissingPackets(header, data); + if (reorder && ctbDbitList.empty()) { + Reorder(size, data); + } + // rearrange ctb digital bits (if ctbDbitlist is not empty) if (!ctbDbitList.empty()) { ArrangeDbitData(size, data); @@ -532,6 +536,88 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { } } +void DataProcessor::Reorder(size_t &size, char *data) { + const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); + const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); + const size_t nTransceiverDataBytes = + generalData->GetNumberOfTransceiverDatabytes(); + + const size_t ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; + + // no digital data + if (ctbDigitalDataBytes == 0) { + LOG(logWARNING) + << "No digital data for call back, yet reorder is set to 1."; + return; + } + + auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); + + const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); + + size_t numBytesPerBit = + 0; // number of bytes per bit in digital data after reordering + + if ((numDigitalSamples % 8) == 0) + numBytesPerBit = numDigitalSamples / 8; + else + numBytesPerBit = numDigitalSamples / 8 + 1; + + size_t totalNumBytes = + numBytesPerBit * + 64; // number of bytes for digital data after reordering + + LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes + << " nAnalogDataBytes:" << nAnalogDataBytes + << " nDigitalDataBytes: " << nDigitalDataBytes + << " ctbDbitOffset:" << ctbDbitOffset + << " nTransceiverDataBytes:" << nTransceiverDataBytes + << " size:" << size << " numsamples:" << numDigitalSamples; + + std::vector result(totalNumBytes, 0); + uint8_t *dest = &result[0]; + + int bitoffset = 0; + // reorder + for (size_t bi = 0; bi < 64; ++bi) { + + if (bitoffset != 0) { + bitoffset = 0; + ++dest; + } + + for (auto *ptr = source; ptr < (source + numDigitalSamples); ++ptr) { + uint8_t bit = (*ptr >> bi) & 1; + *dest |= bit << bitoffset; // most significant bits will be padded + ++bitoffset; + + if (bitoffset == 8) { + bitoffset = 0; + ++dest; + } + } + } + + // move transceiver data to not overwrite and avoid gap in memory + if (totalNumBytes != nDigitalDataBytes) + memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), + data + nAnalogDataBytes + nDigitalDataBytes, + nTransceiverDataBytes); + + // copy back to memory and update size + size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + + nTransceiverDataBytes; + + memcpy(data + nAnalogDataBytes, result.data(), + totalNumBytes * sizeof(uint8_t)); + + LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes + << " nAnalogDataBytes:" << nAnalogDataBytes + << " ctbDbitOffset:" << ctbDbitOffset + << " nTransceiverDataBytes:" << nTransceiverDataBytes + << " size:" << size; +} + /** ctb specific */ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); @@ -589,7 +675,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { for (auto *ptr = source; ptr < (source + numDigitalSamples);) { // get selected bit from each 8 bit uint8_t bit = (*ptr++ >> bi) & 1; - *dest |= bit << bitoffset; + *dest |= bit << bitoffset; // stored as least significant ++bitoffset; // extract destination in 8 bit batches if (bitoffset == 8) { diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 458e750c5..6b4d9d6d9 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -101,6 +101,12 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { */ void ArrangeDbitData(size_t &size, char *data); + /** + * reorder datastream such that individual digital bits from all samples are + * stored consecutively in memory + */ + void Reorder(size_t &size, char *data); + private: void RecordFirstIndex(uint64_t fnum); From 3c79e8d7b2fae623ab3fbf84d50d52948734a89e Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 12 Mar 2025 16:38:18 +0100 Subject: [PATCH 005/103] trailing bits are removed even if reorder false and bitlist empty --- slsReceiverSoftware/src/DataProcessor.cpp | 33 +++++++++++++++++++---- slsReceiverSoftware/src/DataProcessor.h | 7 ++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index ce744e531..b0a3550ae 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -358,13 +358,13 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, if (framePadding && nump < generalData->packetsPerFrame) PadMissingPackets(header, data); - if (reorder && ctbDbitList.empty()) { - Reorder(size, data); - } - - // rearrange ctb digital bits (if ctbDbitlist is not empty) + // rearrange ctb digital bits if (!ctbDbitList.empty()) { ArrangeDbitData(size, data); + } else if (reorder) { + Reorder(size, data); + } else if (ctbDbitOffset > 0) { + RemoveTrailingBits(size, data); } // 'stream Image' check has to be done here before crop image @@ -536,6 +536,29 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { } } +void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { + const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); + const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); + const size_t nTransceiverDataBytes = + generalData->GetNumberOfTransceiverDatabytes(); + + const size_t ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; + + // no digital data + if (ctbDigitalDataBytes == 0) { + LOG(logWARNING) + << "No digital data for call back, yet ctbDbitOffset is non zero."; + return; + } + + // update size and copy data + memmove(data + nAnalogDataBytes, + data + nAnalogDataBytes + ctbDigitalDataBytes, + ctbDigitalDataBytes + nTransceiverDataBytes); + + size = nAnalogDataBytes + ctbDigitalDataBytes + nTransceiverDataBytes; +} + void DataProcessor::Reorder(size_t &size, char *data) { const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 6b4d9d6d9..6febaedbf 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -107,6 +107,11 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { */ void Reorder(size_t &size, char *data); + /** + * remove trailing bits in digital data stream + */ + void RemoveTrailingBits(size_t &size, char *data); + private: void RecordFirstIndex(uint64_t fnum); @@ -172,7 +177,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin {}; + struct timespec timerbegin{}; bool framePadding; std::vector ctbDbitList; bool reorder{false}; // true if data should be reordered TODO: add as mode From e8ac0481141b0b43792ab8b575fda70fbf2aedd4 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 12 Mar 2025 17:13:30 +0100 Subject: [PATCH 006/103] ctb: added command 'rx_dbitreorder' that sets a flag in the receiver to set the reorder flag. By default it is 1. Setting to false means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together --- python/slsdet/detector.py | 10 +++ .../autocomplete/bash_autocomplete.sh | 11 +++- .../autocomplete/zsh_autocomplete.sh | 11 +++- slsDetectorSoftware/generator/commands.yaml | 12 ++++ .../generator/extended_commands.yaml | 41 ++++++++++++ slsDetectorSoftware/include/sls/Detector.h | 9 +++ slsDetectorSoftware/src/Caller.cpp | 62 +++++++++++++++++++ slsDetectorSoftware/src/Caller.h | 2 + slsDetectorSoftware/src/Detector.cpp | 8 +++ slsDetectorSoftware/src/Module.cpp | 9 +++ slsDetectorSoftware/src/Module.h | 2 + slsDetectorSoftware/src/inferAction.cpp | 16 +++++ slsDetectorSoftware/src/inferAction.h | 2 + slsReceiverSoftware/src/ClientInterface.cpp | 23 +++++++ slsReceiverSoftware/src/ClientInterface.h | 2 + slsReceiverSoftware/src/DataProcessor.cpp | 8 +-- slsReceiverSoftware/src/DataProcessor.h | 6 +- slsReceiverSoftware/src/Implementation.cpp | 10 +++ slsReceiverSoftware/src/Implementation.h | 5 ++ .../include/sls/sls_detector_funcs.h | 5 +- 20 files changed, 244 insertions(+), 10 deletions(-) diff --git a/python/slsdet/detector.py b/python/slsdet/detector.py index 459e4a25c..3a34f80ad 100755 --- a/python/slsdet/detector.py +++ b/python/slsdet/detector.py @@ -3463,6 +3463,16 @@ class Detector(CppDetectorApi): def rx_dbitoffset(self, value): ut.set_using_dict(self.setRxDbitOffset, value) + @property + @element + def rx_dbitreorder(self): + """[Ctb] Reorder digital data to group together all samples per signal. Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together.""" + return self.getRxDbitReorder() + + @rx_dbitreorder.setter + def rx_dbitreorder(self, value): + ut.set_using_dict(self.setRxDbitReorder, value) + @property @element def maxadcphaseshift(self): diff --git a/slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh b/slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh index 9345f192d..3ce858ee8 100644 --- a/slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh +++ b/slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh @@ -80,7 +80,7 @@ _sd() { local IS_PATH=0 -local SLS_COMMANDS=" acquire activate adcclk adcenable adcenable10g adcindex adcinvert adclist adcname adcphase adcpipeline adcreg adcvpp apulse asamples autocompdisable badchannels blockingtrigger burstmode burstperiod bursts burstsl bustest cdsgain chipversion clearbit clearbusy clientversion clkdiv clkfreq clkphase collectionmode column compdisabletime confadc config configtransceiver counters currentsource dac dacindex daclist dacname dacvalues datastream dbitclk dbitphase dbitpipeline defaultdac defaultpattern delay delayl detectorserverversion detsize diodelay dpulse dr drlist dsamples execcommand exptime exptime1 exptime2 exptime3 extrastoragecells extsampling extsamplingsrc extsig fformat filtercells filterresistor findex firmwaretest firmwareversion fliprows flowcontrol10g fmaster fname foverwrite fpath framecounter frames framesl frametime free fwrite gaincaps gainmode gappixels gatedelay gatedelay1 gatedelay2 gatedelay3 gates getbit hardwareversion highvoltage hostname im_a im_b im_c im_d im_io imagetest initialchecks inj_ch interpolation interruptsubframe kernelversion lastclient led lock master maxadcphaseshift maxclkphaseshift maxdbitphaseshift measuredperiod measuredsubperiod moduleid nextframenumber nmod numinterfaces overflow packageversion parallel parameters partialreset patfname patioctrl patlimits patloop patloop0 patloop1 patloop2 patmask patnloop patnloop0 patnloop1 patnloop2 patsetbit pattern patternstart patwait patwait0 patwait1 patwait2 patwaittime patwaittime0 patwaittime1 patwaittime2 patword pedestalmode period periodl polarity port powerchip powerindex powerlist powername powervalues programfpga pulse pulsechip pulsenmove pumpprobe quad ratecorr readnrows readout readoutspeed readoutspeedlist rebootcontroller reg resetdacs resetfpga romode row runclk runtime rx_arping rx_clearroi rx_dbitlist rx_dbitoffset rx_discardpolicy rx_fifodepth rx_frameindex rx_framescaught rx_framesperfile rx_hostname rx_jsonaddheader rx_jsonpara rx_lastclient rx_lock rx_missingpackets rx_padding rx_printconfig rx_realudpsocksize rx_roi rx_silent rx_start rx_status rx_stop rx_tcpport rx_threads rx_udpsocksize rx_version rx_zmqfreq rx_zmqhwm rx_zmqip rx_zmqport rx_zmqstartfnum rx_zmqstream samples savepattern scan scanerrmsg selinterface serialnumber setbit settings settingslist settingspath signalindex signallist signalname sleep slowadc slowadcindex slowadclist slowadcname slowadcvalues start status stop stopport storagecell_delay storagecell_start subdeadtime subexptime sync syncclk temp_10ge temp_adc temp_control temp_dcdc temp_event temp_fpga temp_fpgaext temp_fpgafl temp_fpgafr temp_slowadc temp_sodl temp_sodr temp_threshold templist tempvalues tengiga threshold thresholdnotb timing timing_info_decoder timinglist timingsource top transceiverenable trigger triggers triggersl trimbits trimen trimval tsamples txdelay txdelay_frame txdelay_left txdelay_right type udp_cleardst udp_dstip udp_dstip2 udp_dstlist udp_dstmac udp_dstmac2 udp_dstport udp_dstport2 udp_firstdst udp_numdst udp_reconfigure udp_srcip udp_srcip2 udp_srcmac udp_srcmac2 udp_validate update updatedetectorserver updatekernel updatemode user v_a v_b v_c v_chip v_d v_io v_limit vchip_comp_adc vchip_comp_fe vchip_cs vchip_opa_1st vchip_opa_fd vchip_ref_comp_fe versions veto vetoalg vetofile vetophoton vetoref vetostream virtual vm_a vm_b vm_c vm_d vm_io zmqhwm zmqip zmqport " +local SLS_COMMANDS=" acquire activate adcclk adcenable adcenable10g adcindex adcinvert adclist adcname adcphase adcpipeline adcreg adcvpp apulse asamples autocompdisable badchannels blockingtrigger burstmode burstperiod bursts burstsl bustest cdsgain chipversion clearbit clearbusy clientversion clkdiv clkfreq clkphase collectionmode column compdisabletime confadc config configtransceiver counters currentsource dac dacindex daclist dacname dacvalues datastream dbitclk dbitphase dbitpipeline defaultdac defaultpattern delay delayl detectorserverversion detsize diodelay dpulse dr drlist dsamples execcommand exptime exptime1 exptime2 exptime3 extrastoragecells extsampling extsamplingsrc extsig fformat filtercells filterresistor findex firmwaretest firmwareversion fliprows flowcontrol10g fmaster fname foverwrite fpath framecounter frames framesl frametime free fwrite gaincaps gainmode gappixels gatedelay gatedelay1 gatedelay2 gatedelay3 gates getbit hardwareversion highvoltage hostname im_a im_b im_c im_d im_io imagetest initialchecks inj_ch interpolation interruptsubframe kernelversion lastclient led lock master maxadcphaseshift maxclkphaseshift maxdbitphaseshift measuredperiod measuredsubperiod moduleid nextframenumber nmod numinterfaces overflow packageversion parallel parameters partialreset patfname patioctrl patlimits patloop patloop0 patloop1 patloop2 patmask patnloop patnloop0 patnloop1 patnloop2 patsetbit pattern patternstart patwait patwait0 patwait1 patwait2 patwaittime patwaittime0 patwaittime1 patwaittime2 patword pedestalmode period periodl polarity port powerchip powerindex powerlist powername powervalues programfpga pulse pulsechip pulsenmove pumpprobe quad ratecorr readnrows readout readoutspeed readoutspeedlist rebootcontroller reg resetdacs resetfpga romode row runclk runtime rx_arping rx_clearroi rx_dbitlist rx_dbitoffset rx_dbitreorder rx_discardpolicy rx_fifodepth rx_frameindex rx_framescaught rx_framesperfile rx_hostname rx_jsonaddheader rx_jsonpara rx_lastclient rx_lock rx_missingpackets rx_padding rx_printconfig rx_realudpsocksize rx_roi rx_silent rx_start rx_status rx_stop rx_tcpport rx_threads rx_udpsocksize rx_version rx_zmqfreq rx_zmqhwm rx_zmqip rx_zmqport rx_zmqstartfnum rx_zmqstream samples savepattern scan scanerrmsg selinterface serialnumber setbit settings settingslist settingspath signalindex signallist signalname sleep slowadc slowadcindex slowadclist slowadcname slowadcvalues start status stop stopport storagecell_delay storagecell_start subdeadtime subexptime sync syncclk temp_10ge temp_adc temp_control temp_dcdc temp_event temp_fpga temp_fpgaext temp_fpgafl temp_fpgafr temp_slowadc temp_sodl temp_sodr temp_threshold templist tempvalues tengiga threshold thresholdnotb timing timing_info_decoder timinglist timingsource top transceiverenable trigger triggers triggersl trimbits trimen trimval tsamples txdelay txdelay_frame txdelay_left txdelay_right type udp_cleardst udp_dstip udp_dstip2 udp_dstlist udp_dstmac udp_dstmac2 udp_dstport udp_dstport2 udp_firstdst udp_numdst udp_reconfigure udp_srcip udp_srcip2 udp_srcmac udp_srcmac2 udp_validate update updatedetectorserver updatekernel updatemode user v_a v_b v_c v_chip v_d v_io v_limit vchip_comp_adc vchip_comp_fe vchip_cs vchip_opa_1st vchip_opa_fd vchip_ref_comp_fe versions veto vetoalg vetofile vetophoton vetoref vetostream virtual vm_a vm_b vm_c vm_d vm_io zmqhwm zmqip zmqport " __acquire() { FCN_RETURN="" return 0 @@ -2088,6 +2088,15 @@ fi fi return 0 } +__rx_dbitreorder() { +FCN_RETURN="" +if [[ ${IS_GET} -eq 0 ]]; then +if [[ "${cword}" == "2" ]]; then +FCN_RETURN="0 1" +fi +fi +return 0 +} __rx_discardpolicy() { FCN_RETURN="" if [[ ${IS_GET} -eq 0 ]]; then diff --git a/slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh b/slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh index 9eaf07382..c59c1e4a3 100644 --- a/slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh +++ b/slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh @@ -4,7 +4,7 @@ _sd() { -local SLS_COMMANDS=" acquire activate adcclk adcenable adcenable10g adcindex adcinvert adclist adcname adcphase adcpipeline adcreg adcvpp apulse asamples autocompdisable badchannels blockingtrigger burstmode burstperiod bursts burstsl bustest cdsgain chipversion clearbit clearbusy clientversion clkdiv clkfreq clkphase collectionmode column compdisabletime confadc config configtransceiver counters currentsource dac dacindex daclist dacname dacvalues datastream dbitclk dbitphase dbitpipeline defaultdac defaultpattern delay delayl detectorserverversion detsize diodelay dpulse dr drlist dsamples execcommand exptime exptime1 exptime2 exptime3 extrastoragecells extsampling extsamplingsrc extsig fformat filtercells filterresistor findex firmwaretest firmwareversion fliprows flowcontrol10g fmaster fname foverwrite fpath framecounter frames framesl frametime free fwrite gaincaps gainmode gappixels gatedelay gatedelay1 gatedelay2 gatedelay3 gates getbit hardwareversion highvoltage hostname im_a im_b im_c im_d im_io imagetest initialchecks inj_ch interpolation interruptsubframe kernelversion lastclient led lock master maxadcphaseshift maxclkphaseshift maxdbitphaseshift measuredperiod measuredsubperiod moduleid nextframenumber nmod numinterfaces overflow packageversion parallel parameters partialreset patfname patioctrl patlimits patloop patloop0 patloop1 patloop2 patmask patnloop patnloop0 patnloop1 patnloop2 patsetbit pattern patternstart patwait patwait0 patwait1 patwait2 patwaittime patwaittime0 patwaittime1 patwaittime2 patword pedestalmode period periodl polarity port powerchip powerindex powerlist powername powervalues programfpga pulse pulsechip pulsenmove pumpprobe quad ratecorr readnrows readout readoutspeed readoutspeedlist rebootcontroller reg resetdacs resetfpga romode row runclk runtime rx_arping rx_clearroi rx_dbitlist rx_dbitoffset rx_discardpolicy rx_fifodepth rx_frameindex rx_framescaught rx_framesperfile rx_hostname rx_jsonaddheader rx_jsonpara rx_lastclient rx_lock rx_missingpackets rx_padding rx_printconfig rx_realudpsocksize rx_roi rx_silent rx_start rx_status rx_stop rx_tcpport rx_threads rx_udpsocksize rx_version rx_zmqfreq rx_zmqhwm rx_zmqip rx_zmqport rx_zmqstartfnum rx_zmqstream samples savepattern scan scanerrmsg selinterface serialnumber setbit settings settingslist settingspath signalindex signallist signalname sleep slowadc slowadcindex slowadclist slowadcname slowadcvalues start status stop stopport storagecell_delay storagecell_start subdeadtime subexptime sync syncclk temp_10ge temp_adc temp_control temp_dcdc temp_event temp_fpga temp_fpgaext temp_fpgafl temp_fpgafr temp_slowadc temp_sodl temp_sodr temp_threshold templist tempvalues tengiga threshold thresholdnotb timing timing_info_decoder timinglist timingsource top transceiverenable trigger triggers triggersl trimbits trimen trimval tsamples txdelay txdelay_frame txdelay_left txdelay_right type udp_cleardst udp_dstip udp_dstip2 udp_dstlist udp_dstmac udp_dstmac2 udp_dstport udp_dstport2 udp_firstdst udp_numdst udp_reconfigure udp_srcip udp_srcip2 udp_srcmac udp_srcmac2 udp_validate update updatedetectorserver updatekernel updatemode user v_a v_b v_c v_chip v_d v_io v_limit vchip_comp_adc vchip_comp_fe vchip_cs vchip_opa_1st vchip_opa_fd vchip_ref_comp_fe versions veto vetoalg vetofile vetophoton vetoref vetostream virtual vm_a vm_b vm_c vm_d vm_io zmqhwm zmqip zmqport " +local SLS_COMMANDS=" acquire activate adcclk adcenable adcenable10g adcindex adcinvert adclist adcname adcphase adcpipeline adcreg adcvpp apulse asamples autocompdisable badchannels blockingtrigger burstmode burstperiod bursts burstsl bustest cdsgain chipversion clearbit clearbusy clientversion clkdiv clkfreq clkphase collectionmode column compdisabletime confadc config configtransceiver counters currentsource dac dacindex daclist dacname dacvalues datastream dbitclk dbitphase dbitpipeline defaultdac defaultpattern delay delayl detectorserverversion detsize diodelay dpulse dr drlist dsamples execcommand exptime exptime1 exptime2 exptime3 extrastoragecells extsampling extsamplingsrc extsig fformat filtercells filterresistor findex firmwaretest firmwareversion fliprows flowcontrol10g fmaster fname foverwrite fpath framecounter frames framesl frametime free fwrite gaincaps gainmode gappixels gatedelay gatedelay1 gatedelay2 gatedelay3 gates getbit hardwareversion highvoltage hostname im_a im_b im_c im_d im_io imagetest initialchecks inj_ch interpolation interruptsubframe kernelversion lastclient led lock master maxadcphaseshift maxclkphaseshift maxdbitphaseshift measuredperiod measuredsubperiod moduleid nextframenumber nmod numinterfaces overflow packageversion parallel parameters partialreset patfname patioctrl patlimits patloop patloop0 patloop1 patloop2 patmask patnloop patnloop0 patnloop1 patnloop2 patsetbit pattern patternstart patwait patwait0 patwait1 patwait2 patwaittime patwaittime0 patwaittime1 patwaittime2 patword pedestalmode period periodl polarity port powerchip powerindex powerlist powername powervalues programfpga pulse pulsechip pulsenmove pumpprobe quad ratecorr readnrows readout readoutspeed readoutspeedlist rebootcontroller reg resetdacs resetfpga romode row runclk runtime rx_arping rx_clearroi rx_dbitlist rx_dbitoffset rx_dbitreorder rx_discardpolicy rx_fifodepth rx_frameindex rx_framescaught rx_framesperfile rx_hostname rx_jsonaddheader rx_jsonpara rx_lastclient rx_lock rx_missingpackets rx_padding rx_printconfig rx_realudpsocksize rx_roi rx_silent rx_start rx_status rx_stop rx_tcpport rx_threads rx_udpsocksize rx_version rx_zmqfreq rx_zmqhwm rx_zmqip rx_zmqport rx_zmqstartfnum rx_zmqstream samples savepattern scan scanerrmsg selinterface serialnumber setbit settings settingslist settingspath signalindex signallist signalname sleep slowadc slowadcindex slowadclist slowadcname slowadcvalues start status stop stopport storagecell_delay storagecell_start subdeadtime subexptime sync syncclk temp_10ge temp_adc temp_control temp_dcdc temp_event temp_fpga temp_fpgaext temp_fpgafl temp_fpgafr temp_slowadc temp_sodl temp_sodr temp_threshold templist tempvalues tengiga threshold thresholdnotb timing timing_info_decoder timinglist timingsource top transceiverenable trigger triggers triggersl trimbits trimen trimval tsamples txdelay txdelay_frame txdelay_left txdelay_right type udp_cleardst udp_dstip udp_dstip2 udp_dstlist udp_dstmac udp_dstmac2 udp_dstport udp_dstport2 udp_firstdst udp_numdst udp_reconfigure udp_srcip udp_srcip2 udp_srcmac udp_srcmac2 udp_validate update updatedetectorserver updatekernel updatemode user v_a v_b v_c v_chip v_d v_io v_limit vchip_comp_adc vchip_comp_fe vchip_cs vchip_opa_1st vchip_opa_fd vchip_ref_comp_fe versions veto vetoalg vetofile vetophoton vetoref vetostream virtual vm_a vm_b vm_c vm_d vm_io zmqhwm zmqip zmqport " __acquire() { FCN_RETURN="" return 0 @@ -2012,6 +2012,15 @@ fi fi return 0 } +__rx_dbitreorder() { +FCN_RETURN="" +if [[ ${IS_GET} -eq 0 ]]; then +if [[ "${cword}" == "2" ]]; then +FCN_RETURN="0 1" +fi +fi +return 0 +} __rx_discardpolicy() { FCN_RETURN="" if [[ ${IS_GET} -eq 0 ]]; then diff --git a/slsDetectorSoftware/generator/commands.yaml b/slsDetectorSoftware/generator/commands.yaml index 95c4f0ca8..47e2e1898 100644 --- a/slsDetectorSoftware/generator/commands.yaml +++ b/slsDetectorSoftware/generator/commands.yaml @@ -1412,6 +1412,18 @@ lock: function: setDetectorLock input_types: [ bool ] + +rx_dbitreorder: + help: "[0, 1]\n\t[Ctb] Reorder digital data to group together all samples per signal. Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together." + inherit_actions: INTEGER_COMMAND_VEC_ID + actions: + GET: + function: getRxDbitReorder + PUT: + function: setRxDbitReorder + input_types: [ bool ] + + ################# INTEGER_COMMAND_VEC_ID_GET ################# master: diff --git a/slsDetectorSoftware/generator/extended_commands.yaml b/slsDetectorSoftware/generator/extended_commands.yaml index 908cc2dfb..4e4b52a72 100644 --- a/slsDetectorSoftware/generator/extended_commands.yaml +++ b/slsDetectorSoftware/generator/extended_commands.yaml @@ -8305,6 +8305,47 @@ rx_dbitoffset: help: "[n_bytes]\n\t[Ctb] Offset in bytes in digital data to skip in receiver." infer_action: true template: true +rx_dbitreorder: + actions: + GET: + args: + - arg_types: [] + argc: 0 + cast_input: [] + check_det_id: false + convert_det_id: true + function: getRxDbitReorder + input: [] + input_types: [] + output: + - OutString(t) + require_det_id: true + store_result_in_t: true + PUT: + args: + - arg_types: + - bool + argc: 1 + cast_input: + - true + check_det_id: false + convert_det_id: true + function: setRxDbitReorder + input: + - args[0] + input_types: + - bool + output: + - args.front() + require_det_id: true + store_result_in_t: false + command_name: rx_dbitreorder + function_alias: rx_dbitreorder + help: "[0, 1]\n\t[Ctb] Reorder digital data to group together all samples per signal.\ + \ Default is 1. Setting to 0 means 'do not reorder' and to keep what the board\ + \ spits out, which is that all signals in a sample are grouped together." + infer_action: true + template: true rx_discardpolicy: actions: GET: diff --git a/slsDetectorSoftware/include/sls/Detector.h b/slsDetectorSoftware/include/sls/Detector.h index a58265324..8096f75a7 100644 --- a/slsDetectorSoftware/include/sls/Detector.h +++ b/slsDetectorSoftware/include/sls/Detector.h @@ -1729,6 +1729,15 @@ class Detector { /** [CTB] Set number of bytes of digital data to skip in the Receiver */ void setRxDbitOffset(int value, Positions pos = {}); + /** [CTB] */ + Result getRxDbitReorder(Positions pos = {}) const; + + /** [CTB] Reorder digital data to group together all samples per signal. + * Default is true. Setting to false means 'do not reorder' and to keep what + * the board spits out, which is that all signals in a sample are grouped + * together */ + void setRxDbitReorder(bool reorder, Positions pos = {}); + /** * [CTB] Set Digital IO Delay * cannot get diff --git a/slsDetectorSoftware/src/Caller.cpp b/slsDetectorSoftware/src/Caller.cpp index da761afa2..c284b1957 100644 --- a/slsDetectorSoftware/src/Caller.cpp +++ b/slsDetectorSoftware/src/Caller.cpp @@ -10700,6 +10700,68 @@ std::string Caller::rx_dbitoffset(int action) { return os.str(); } +std::string Caller::rx_dbitreorder(int action) { + + std::ostringstream os; + // print help + if (action == slsDetectorDefs::HELP_ACTION) { + os << R"V0G0N([0, 1] + [Ctb] Reorder digital data to group together all samples per signal. Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together. )V0G0N" + << std::endl; + return os.str(); + } + + // check if action and arguments are valid + if (action == slsDetectorDefs::GET_ACTION) { + if (1 && args.size() != 0) { + throw RuntimeError("Wrong number of arguments for action GET"); + } + + if (args.size() == 0) { + } + + } + + else if (action == slsDetectorDefs::PUT_ACTION) { + if (1 && args.size() != 1) { + throw RuntimeError("Wrong number of arguments for action PUT"); + } + + if (args.size() == 1) { + try { + StringTo(args[0]); + } catch (...) { + throw RuntimeError("Could not convert argument 0 to bool"); + } + } + + } + + else { + + throw RuntimeError("INTERNAL ERROR: Invalid action: supported actions " + "are ['GET', 'PUT']"); + } + + // generate code for each action + if (action == slsDetectorDefs::GET_ACTION) { + if (args.size() == 0) { + auto t = det->getRxDbitReorder(std::vector{det_id}); + os << OutString(t) << '\n'; + } + } + + if (action == slsDetectorDefs::PUT_ACTION) { + if (args.size() == 1) { + auto arg0 = StringTo(args[0]); + det->setRxDbitReorder(arg0, std::vector{det_id}); + os << args.front() << '\n'; + } + } + + return os.str(); +} + std::string Caller::rx_discardpolicy(int action) { std::ostringstream os; diff --git a/slsDetectorSoftware/src/Caller.h b/slsDetectorSoftware/src/Caller.h index d7cfbdd02..3e1be13ac 100644 --- a/slsDetectorSoftware/src/Caller.h +++ b/slsDetectorSoftware/src/Caller.h @@ -236,6 +236,7 @@ class Caller { std::string rx_clearroi(int action); std::string rx_dbitlist(int action); std::string rx_dbitoffset(int action); + std::string rx_dbitreorder(int action); std::string rx_discardpolicy(int action); std::string rx_fifodepth(int action); std::string rx_frameindex(int action); @@ -582,6 +583,7 @@ class Caller { {"rx_clearroi", &Caller::rx_clearroi}, {"rx_dbitlist", &Caller::rx_dbitlist}, {"rx_dbitoffset", &Caller::rx_dbitoffset}, + {"rx_dbitreorder", &Caller::rx_dbitreorder}, {"rx_discardpolicy", &Caller::rx_discardpolicy}, {"rx_fifodepth", &Caller::rx_fifodepth}, {"rx_frameindex", &Caller::rx_frameindex}, diff --git a/slsDetectorSoftware/src/Detector.cpp b/slsDetectorSoftware/src/Detector.cpp index 2e85c7c2c..f84e5fbb3 100644 --- a/slsDetectorSoftware/src/Detector.cpp +++ b/slsDetectorSoftware/src/Detector.cpp @@ -2277,6 +2277,14 @@ void Detector::setRxDbitOffset(int value, Positions pos) { pimpl->Parallel(&Module::setReceiverDbitOffset, pos, value); } +Result Detector::getRxDbitReorder(Positions pos) const { + return pimpl->Parallel(&Module::getReceiverDbitReorder, pos); +} + +void Detector::setRxDbitReorder(bool reorder, Positions pos) { + pimpl->Parallel(&Module::setReceiverDbitReorder, pos, reorder); +} + void Detector::setDigitalIODelay(uint64_t pinMask, int delay, Positions pos) { pimpl->Parallel(&Module::setDigitalIODelay, pos, pinMask, delay); } diff --git a/slsDetectorSoftware/src/Module.cpp b/slsDetectorSoftware/src/Module.cpp index 5b535b92a..d1356c30f 100644 --- a/slsDetectorSoftware/src/Module.cpp +++ b/slsDetectorSoftware/src/Module.cpp @@ -2509,6 +2509,15 @@ void Module::setReceiverDbitOffset(int value) { sendToReceiver(F_SET_RECEIVER_DBIT_OFFSET, value, nullptr); } +bool Module::getReceiverDbitReorder() const { + return sendToReceiver(F_GET_RECEIVER_DBIT_REORDER); +} + +void Module::setReceiverDbitReorder(bool reorder) { + sendToReceiver(F_SET_RECEIVER_DBIT_REORDER, static_cast(reorder), + nullptr); +} + void Module::setDigitalIODelay(uint64_t pinMask, int delay) { uint64_t args[]{pinMask, static_cast(delay)}; sendToDetector(F_DIGITAL_IO_DELAY, args, nullptr); diff --git a/slsDetectorSoftware/src/Module.h b/slsDetectorSoftware/src/Module.h index 18fbaa296..86c9e3d5f 100644 --- a/slsDetectorSoftware/src/Module.h +++ b/slsDetectorSoftware/src/Module.h @@ -510,6 +510,8 @@ class Module : public virtual slsDetectorDefs { void setReceiverDbitList(std::vector list); int getReceiverDbitOffset() const; void setReceiverDbitOffset(int value); + bool getReceiverDbitReorder() const; + void setReceiverDbitReorder(bool value); void setDigitalIODelay(uint64_t pinMask, int delay); bool getLEDEnable() const; void setLEDEnable(bool enable); diff --git a/slsDetectorSoftware/src/inferAction.cpp b/slsDetectorSoftware/src/inferAction.cpp index 56ff942cd..68c8638f1 100644 --- a/slsDetectorSoftware/src/inferAction.cpp +++ b/slsDetectorSoftware/src/inferAction.cpp @@ -2568,6 +2568,22 @@ int InferAction::rx_dbitoffset() { } } +int InferAction::rx_dbitreorder() { + + if (args.size() == 0) { + return slsDetectorDefs::GET_ACTION; + } + + if (args.size() == 1) { + return slsDetectorDefs::PUT_ACTION; + } + + else { + + throw RuntimeError("Could not infer action: Wrong number of arguments"); + } +} + int InferAction::rx_discardpolicy() { if (args.size() == 0) { diff --git a/slsDetectorSoftware/src/inferAction.h b/slsDetectorSoftware/src/inferAction.h index 35d1109ef..ffc6944d7 100644 --- a/slsDetectorSoftware/src/inferAction.h +++ b/slsDetectorSoftware/src/inferAction.h @@ -193,6 +193,7 @@ class InferAction { int rx_clearroi(); int rx_dbitlist(); int rx_dbitoffset(); + int rx_dbitreorder(); int rx_discardpolicy(); int rx_fifodepth(); int rx_frameindex(); @@ -527,6 +528,7 @@ class InferAction { {"rx_clearroi", &InferAction::rx_clearroi}, {"rx_dbitlist", &InferAction::rx_dbitlist}, {"rx_dbitoffset", &InferAction::rx_dbitoffset}, + {"rx_dbitreorder", &InferAction::rx_dbitreorder}, {"rx_discardpolicy", &InferAction::rx_discardpolicy}, {"rx_fifodepth", &InferAction::rx_fifodepth}, {"rx_frameindex", &InferAction::rx_frameindex}, diff --git a/slsReceiverSoftware/src/ClientInterface.cpp b/slsReceiverSoftware/src/ClientInterface.cpp index a75a87d2f..0180af8fb 100644 --- a/slsReceiverSoftware/src/ClientInterface.cpp +++ b/slsReceiverSoftware/src/ClientInterface.cpp @@ -218,6 +218,8 @@ int ClientInterface::functionTable(){ flist[F_RECEIVER_SET_TRANSCEIVER_MASK] = &ClientInterface::set_transceiver_mask; flist[F_RECEIVER_SET_ROW] = &ClientInterface::set_row; flist[F_RECEIVER_SET_COLUMN] = &ClientInterface::set_column; + flist[F_GET_RECEIVER_DBIT_REORDER] = &ClientInterface::get_dbit_reorder; + flist[F_SET_RECEIVER_DBIT_REORDER] = &ClientInterface::set_dbit_reorder; for (int i = NUM_DET_FUNCTIONS + 1; i < NUM_REC_FUNCTIONS ; i++) { @@ -1789,4 +1791,25 @@ int ClientInterface::set_column(Interface &socket) { return socket.Send(OK); } +int ClientInterface::get_dbit_reorder(Interface &socket) { + if (detType != CHIPTESTBOARD && detType != XILINX_CHIPTESTBOARD) + functionNotImplemented(); + int retval = impl()->getDbitReorder(); + LOG(logDEBUG1) << "Dbit reorder retval: " << retval; + return socket.sendResult(retval); +} + +int ClientInterface::set_dbit_reorder(Interface &socket) { + auto arg = socket.Receive(); + if (detType != CHIPTESTBOARD && detType != XILINX_CHIPTESTBOARD) + functionNotImplemented(); + if (arg < 0) { + throw RuntimeError("Invalid dbit reorder: " + std::to_string(arg)); + } + verifyIdle(socket); + LOG(logDEBUG1) << "Setting Dbit offset: " << arg; + impl()->setDbitReorder(arg); + return socket.Send(OK); +} + } // namespace sls diff --git a/slsReceiverSoftware/src/ClientInterface.h b/slsReceiverSoftware/src/ClientInterface.h index a6749e33f..665e0f396 100644 --- a/slsReceiverSoftware/src/ClientInterface.h +++ b/slsReceiverSoftware/src/ClientInterface.h @@ -164,6 +164,8 @@ class ClientInterface : private virtual slsDetectorDefs { int set_transceiver_mask(ServerInterface &socket); int set_row(ServerInterface &socket); int set_column(ServerInterface &socket); + int get_dbit_reorder(ServerInterface &socket); + int set_dbit_reorder(ServerInterface &socket); Implementation *impl() { if (receiver != nullptr) { diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index e80ab531e..35b17f538 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -75,10 +75,10 @@ void DataProcessor::SetCtbDbitList(std::vector value) { ctbDbitList = value; } -void DataProcessor::SetReorder(const bool value) { reorder = value; } - void DataProcessor::SetCtbDbitOffset(int value) { ctbDbitOffset = value; } +void DataProcessor::SetCtbDbitReorder(bool value) { ctbDbitReorder = value; } + void DataProcessor::SetQuadEnable(bool value) { quadEnable = value; } void DataProcessor::SetFlipRows(bool fd) { @@ -556,7 +556,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { 0; // number of bytes for selected digital data given by dtbDbitList // store each selected bit from all samples consecutively - if (reorder) { + if (ctbDbitReorder) { int numBitsPerDbit = numDigitalSamples; // num bits per selected digital // Bit for all samples if ((numBitsPerDbit % 8) != 0) @@ -575,7 +575,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { std::vector result(totalNumBytes, 0); uint8_t *dest = &result[0]; - if (reorder) { + if (ctbDbitReorder) { // loop through digital bit enable vector int bitoffset = 0; for (auto bi : ctbDbitList) { diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index d09a9ca3f..30873a1f9 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -46,8 +46,8 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { void SetStreamingStartFnum(uint32_t value); void SetFramePadding(bool enable); void SetCtbDbitList(std::vector value); - void SetReorder(const bool reorder); void SetCtbDbitOffset(int value); + void SetCtbDbitReorder(bool value); void SetQuadEnable(bool value); void SetFlipRows(bool fd); void SetNumberofTotalFrames(uint64_t value); @@ -168,8 +168,8 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { struct timespec timerbegin {}; bool framePadding; std::vector ctbDbitList; - bool reorder{false}; // true if data should be reordered TODO: add as mode - int ctbDbitOffset; + int ctbDbitOffset{0}; + bool ctbDbitReorder{false}; std::atomic startedFlag{false}; std::atomic firstIndex{0}; bool quadEnable{false}; diff --git a/slsReceiverSoftware/src/Implementation.cpp b/slsReceiverSoftware/src/Implementation.cpp index 8104e8c7a..ea636258c 100644 --- a/slsReceiverSoftware/src/Implementation.cpp +++ b/slsReceiverSoftware/src/Implementation.cpp @@ -202,6 +202,7 @@ void Implementation::SetupDataProcessor(int i) { dataProcessor[i]->SetFramePadding(framePadding); dataProcessor[i]->SetCtbDbitList(ctbDbitList); dataProcessor[i]->SetCtbDbitOffset(ctbDbitOffset); + dataProcessor[i]->SetCtbDbitReorder(ctbDbitReorder); dataProcessor[i]->SetQuadEnable(quadEnable); dataProcessor[i]->SetFlipRows(flipRows); dataProcessor[i]->SetNumberofTotalFrames(numberOfTotalFrames); @@ -1766,6 +1767,15 @@ void Implementation::setDbitOffset(const int s) { LOG(logINFO) << "Dbit offset: " << ctbDbitOffset; } +bool Implementation::getDbitReorder() const { return ctbDbitReorder; } + +void Implementation::setDbitReorder(const bool reorder) { + ctbDbitReorder = reorder; + for (const auto &it : dataProcessor) + it->SetCtbDbitReorder(ctbDbitReorder); + LOG(logINFO) << "Dbit reorder: " << ctbDbitReorder; +} + uint32_t Implementation::getTransceiverEnableMask() const { return generalData->transceiverMask; } diff --git a/slsReceiverSoftware/src/Implementation.h b/slsReceiverSoftware/src/Implementation.h index 70ceb4a54..79baa670b 100644 --- a/slsReceiverSoftware/src/Implementation.h +++ b/slsReceiverSoftware/src/Implementation.h @@ -252,6 +252,10 @@ class Implementation : private virtual slsDetectorDefs { int getDbitOffset() const; /* [Ctb] */ void setDbitOffset(const int s); + bool getDbitReorder() const; + /* [Ctb] */ + void setDbitReorder(const bool reorder); + uint32_t getTransceiverEnableMask() const; /* [Ctb] */ void setTransceiverEnableMask(const uint32_t mask); @@ -368,6 +372,7 @@ class Implementation : private virtual slsDetectorDefs { std::vector rateCorrections; std::vector ctbDbitList; int ctbDbitOffset{0}; + bool ctbDbitReorder{true}; // callbacks void (*startAcquisitionCallBack)(const startCallbackHeader, diff --git a/slsSupportLib/include/sls/sls_detector_funcs.h b/slsSupportLib/include/sls/sls_detector_funcs.h index 8a7ef3508..ec1c6d2f1 100755 --- a/slsSupportLib/include/sls/sls_detector_funcs.h +++ b/slsSupportLib/include/sls/sls_detector_funcs.h @@ -410,6 +410,8 @@ enum detFuncs { F_RECEIVER_SET_TRANSCEIVER_MASK, F_RECEIVER_SET_ROW, F_RECEIVER_SET_COLUMN, + F_GET_RECEIVER_DBIT_REORDER, + F_SET_RECEIVER_DBIT_REORDER, NUM_REC_FUNCTIONS }; @@ -816,7 +818,8 @@ const char* getFunctionNameFromEnum(enum detFuncs func) { case F_RECEIVER_SET_TRANSCEIVER_MASK: return "F_RECEIVER_SET_TRANSCEIVER_MASK"; case F_RECEIVER_SET_ROW: return "F_RECEIVER_SET_ROW"; case F_RECEIVER_SET_COLUMN: return "F_RECEIVER_SET_COLUMN"; - + case F_GET_RECEIVER_DBIT_REORDER: return "F_GET_RECEIVER_DBIT_REORDER"; + case F_SET_RECEIVER_DBIT_REORDER: return "F_SET_RECEIVER_DBIT_REORDER"; case NUM_REC_FUNCTIONS: return "NUM_REC_FUNCTIONS"; default: return "Unknown Function"; From ff101e19cd98771b944a146992ac86f319d51f2f Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 12 Mar 2025 17:17:40 +0100 Subject: [PATCH 007/103] added pybind for it --- python/src/detector.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/src/detector.cpp b/python/src/detector.cpp index 2d2c224b7..5393cbf6e 100644 --- a/python/src/detector.cpp +++ b/python/src/detector.cpp @@ -1664,6 +1664,14 @@ void init_det(py::module &m) { (void (Detector::*)(int, sls::Positions)) & Detector::setRxDbitOffset, py::arg(), py::arg() = Positions{}); + CppDetectorApi.def("getRxDbitReorder", + (Result(Detector::*)(sls::Positions) const) & + Detector::getRxDbitReorder, + py::arg() = Positions{}); + CppDetectorApi.def("setRxDbitReorder", + (void (Detector::*)(bool, sls::Positions)) & + Detector::setRxDbitReorder, + py::arg(), py::arg() = Positions{}); CppDetectorApi.def("setDigitalIODelay", (void (Detector::*)(uint64_t, int, sls::Positions)) & Detector::setDigitalIODelay, From e1c9754cd2dca111e080bc776c550a0f697c8936 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 12 Mar 2025 17:21:05 +0100 Subject: [PATCH 008/103] Added test for rx_dbitreorder command --- .../tests/Caller/test-Caller-rx.cpp | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp index 38fe14e86..d04890b0a 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp @@ -956,6 +956,42 @@ TEST_CASE("rx_dbitoffset", "[.cmdcall][.rx]") { } } +TEST_CASE("rx_dbitreorder", "[.cmdcall][.rx]") { + Detector det; + Caller caller(&det); + auto det_type = det.getDetectorType().squash(); + if (det_type == defs::CHIPTESTBOARD || + det_type == defs::XILINX_CHIPTESTBOARD) { + auto prev_val = det.getRxDbitReorder(); + { + std::ostringstream oss; + caller.call("rx_dbitreorder", {"1"}, -1, PUT, oss); + REQUIRE(oss.str() == "rx_dbitreorder 1\n"); + } + { + std::ostringstream oss; + caller.call("rx_dbitreorder", {"0"}, -1, PUT, oss); + REQUIRE(oss.str() == "rx_dbitreorder 0\n"); + } + { + std::ostringstream oss; + caller.call("rx_dbitreorder", {"1"}, -1, PUT, oss); + REQUIRE(oss.str() == "rx_dbitreorder 1\n"); + } + { + std::ostringstream oss; + caller.call("rx_dbitreorder", {}, -1, GET, oss); + REQUIRE(oss.str() == "rx_dbitreorder 1\n"); + } + REQUIRE_THROWS(caller.call("rx_dbitreorder", {"15"}, -1, PUT)); + for (int i = 0; i != det.size(); ++i) { + det.setRxDbitReorder(prev_val[i], {i}); + } + } else { + REQUIRE_THROWS(caller.call("rx_dbitoffset", {}, -1, GET)); + } +} + TEST_CASE("rx_jsonaddheader", "[.cmdcall][.rx]") { Detector det; Caller caller(&det); From bd66228b30f982d44bdc26c33ba023e0ffb58fcb Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 13 Mar 2025 11:14:01 +0100 Subject: [PATCH 009/103] minor to check workflow --- slsReceiverSoftware/src/DataProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 35b17f538..a6dbd8cf7 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -3,7 +3,7 @@ /************************************************ * @file DataProcessor.cpp * @short creates data processor thread that - * pulls pointers to memory addresses from fifos + * pulls pointers to memory addresses from fifos * and processes data stored in them & writes them to file ***********************************************/ From 6e5b058fc1a51714c11e93cdf04df28fe9059882 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 13 Mar 2025 13:17:54 +0100 Subject: [PATCH 010/103] merge fix --- slsReceiverSoftware/src/DataProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 712956c0d..657bbb98f 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -361,7 +361,7 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, // rearrange ctb digital bits if (!ctbDbitList.empty()) { ArrangeDbitData(size, data); - } else if (reorder) { + } else if (ctbDbitReorder) { Reorder(size, data); } else if (ctbDbitOffset > 0) { RemoveTrailingBits(size, data); From 0a5b5aac4bb9dff039808ffba998ead9a11c7bd6 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Fri, 14 Mar 2025 14:04:55 +0100 Subject: [PATCH 011/103] added unit tests for dataprocessor rearranging functions --- slsReceiverSoftware/src/DataProcessor.cpp | 16 +- slsReceiverSoftware/src/DataProcessor.h | 4 +- .../tests/test-ArrangeDataBasedOnBitList.cpp | 415 ++++++++++++++---- 3 files changed, 347 insertions(+), 88 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index b0a3550ae..222917d4e 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -552,8 +552,7 @@ void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { } // update size and copy data - memmove(data + nAnalogDataBytes, - data + nAnalogDataBytes + ctbDigitalDataBytes, + memmove(data + nAnalogDataBytes, data + nAnalogDataBytes + ctbDbitOffset, ctbDigitalDataBytes + nTransceiverDataBytes); size = nAnalogDataBytes + ctbDigitalDataBytes + nTransceiverDataBytes; @@ -575,6 +574,7 @@ void DataProcessor::Reorder(size_t &size, char *data) { } auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); + // TODO: leads to unaligned data const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -590,13 +590,6 @@ void DataProcessor::Reorder(size_t &size, char *data) { numBytesPerBit * 64; // number of bytes for digital data after reordering - LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes - << " nAnalogDataBytes:" << nAnalogDataBytes - << " nDigitalDataBytes: " << nDigitalDataBytes - << " ctbDbitOffset:" << ctbDbitOffset - << " nTransceiverDataBytes:" << nTransceiverDataBytes - << " size:" << size << " numsamples:" << numDigitalSamples; - std::vector result(totalNumBytes, 0); uint8_t *dest = &result[0]; @@ -666,8 +659,9 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { // store each selected bit from all samples consecutively if (reorder) { - int numBitsPerDbit = numDigitalSamples; // num bits per selected digital - // Bit for all samples + size_t numBitsPerDbit = + numDigitalSamples; // num bits per selected digital + // Bit for all samples if ((numBitsPerDbit % 8) != 0) numBitsPerDbit += (8 - (numDigitalSamples % 8)); totalNumBytes = (numBitsPerDbit / 8) * ctbDbitList.size(); diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 6febaedbf..3e0aa0b9d 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -177,11 +177,11 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin{}; + struct timespec timerbegin {}; bool framePadding; std::vector ctbDbitList; bool reorder{false}; // true if data should be reordered TODO: add as mode - int ctbDbitOffset; + int ctbDbitOffset{0}; std::atomic startedFlag{false}; std::atomic firstIndex{0}; bool quadEnable{false}; diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index 3012ddf64..ad5a8fbb3 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -2,7 +2,7 @@ // Copyright (C) 2025 Contributors to the SLS Detector Package /************************************************ * @file test-ArrangeDataBasedOnBitList.cpp - * @short test case for DataProcessor member function ArrangeDbitData, + * @short test case for DataProcessor rearrange functions, ***********************************************/ #include "DataProcessor.h" @@ -36,7 +36,7 @@ class GeneralDataTest : public GeneralData { int nTransceiverBytes; }; -// oke maybe just make it static +// dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: DataProcessorTest() : DataProcessor(0){}; @@ -44,91 +44,356 @@ class DataProcessorTest : public DataProcessor { void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } -}; -TEST_CASE("Arrange with reorder false") { - DataProcessorTest dataprocessor; - - std::vector bitlist{1, 4, 5}; - dataprocessor.SetCtbDbitList(bitlist); - - size_t num_digital_samples = 5; // size_t or uint8_t ? - size_t num_digital_bytes = num_digital_samples * 8; - size_t num_analog_bytes = 1; - size_t num_transceiver_bytes = 2; - size_t random_offset_bytes = 0; - - size_t size = num_analog_bytes + num_digital_bytes + num_transceiver_bytes + - random_offset_bytes; - - char *data = new char[size]; - - char dummy_value = static_cast(125); - memset(data, dummy_value, num_analog_bytes); - memset(data + num_analog_bytes, 0xFF, - num_digital_bytes); // all digital bits are one - memset(data + num_digital_bytes + num_analog_bytes, dummy_value, - num_transceiver_bytes); - - GeneralDataTest *generaldata = new GeneralDataTest; - generaldata->SetNumberOfAnalogDatabytes(num_analog_bytes); - generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes); - generaldata->SetNumberOfTransceiverDatabytes(num_transceiver_bytes); - - dataprocessor.SetGeneralData(generaldata); - - size_t new_num_digital_bytes = - (bitlist.size() / 8 + static_cast(bitlist.size() % 8 != 0)) * - num_digital_samples; - - size_t new_size = - num_analog_bytes + num_transceiver_bytes + new_num_digital_bytes; - - char *new_data = new char[new_size]; - - memset(new_data, dummy_value, num_analog_bytes); - - // TODO: Make test more explicit and less generic - size_t num_bytes_for_bitlist = - bitlist.size() / 8 + static_cast(bitlist.size() % 8 != 0); - - // 125 7 7 7 7 7 125 125 - for (size_t sample = 0; sample < num_digital_samples; ++sample) { - if (bitlist.size() / 8 != 0) { - memset(new_data + sample * num_bytes_for_bitlist + num_analog_bytes, - 0xFF, - bitlist.size() / 8); // set to 1 - } else if (bitlist.size() % 8 != 0) { - memset(new_data + sample * num_bytes_for_bitlist + - bitlist.size() / 8 + num_analog_bytes, - static_cast(pow(2, (bitlist.size() % 8)) - 1), - 1); // convert binary number to decimal - } + void Reorder(size_t &size, char *data) { + DataProcessor::Reorder(size, data); } - memset(new_data + new_num_digital_bytes + num_analog_bytes, dummy_value, - num_transceiver_bytes); + void RemoveTrailingBits(size_t &size, char *data) { + DataProcessor::RemoveTrailingBits(size, data); + } +}; - dataprocessor.ArrangeDbitData(size, data); +/** + * test fixture for Testing, + * num_analog_bytes = 1 byte has a value of 125 + * num_transceiver_bytes = 2 both bytes have a value of 125 + * num_digital_bytes is variable and is defined by number of samples + * default num sample is 5 + * all bytes in digital data take a value of 255 + */ +class DataProcessorTestFixture { + public: + DataProcessorTestFixture() { + // setup Test Fixture + dataprocessor = new DataProcessorTest; + generaldata = new GeneralDataTest; - CHECK(size == new_size); + // set_num_samples(num_samples); - CHECK(memcmp(data, new_data, size) == 0); + generaldata->SetNumberOfAnalogDatabytes(num_analog_bytes); + generaldata->SetNumberOfTransceiverDatabytes(num_transceiver_bytes); + generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes + + num_random_offset_bytes); - // Free allocated memory - delete[] data; - delete[] new_data; - delete generaldata; + dataprocessor->SetGeneralData(generaldata); + } + + ~DataProcessorTestFixture() { + delete[] data; + delete dataprocessor; + delete generaldata; + } + + size_t get_size() const { + return num_analog_bytes + num_digital_bytes + num_transceiver_bytes + + num_random_offset_bytes; + } + + void set_num_samples(const size_t value) { + num_samples = value; + num_digital_bytes = num_samples * 8; // 64 (8 bytes) per sample + + generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes + + num_random_offset_bytes); + } + + void set_random_offset_bytes(const size_t value) { + num_random_offset_bytes = value; + generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes + + num_random_offset_bytes); + } + + void set_data() { + delete[] data; + data = new char[get_size()]; + + // set testing data + memset(data, dummy_value, num_analog_bytes); // set to dummy value + memset(data + num_analog_bytes, 0, + num_random_offset_bytes); // set to zero + memset(data + num_analog_bytes + num_random_offset_bytes, 0xFF, + num_digital_bytes); // all digital bits are one + memset(data + num_digital_bytes + num_analog_bytes + + num_random_offset_bytes, + dummy_value, + num_transceiver_bytes); // set to dummy value + } + + DataProcessorTest *dataprocessor; + GeneralDataTest *generaldata; + const size_t num_analog_bytes = 1; + const size_t num_transceiver_bytes = 2; + const char dummy_value = static_cast(125); + size_t num_digital_bytes = 40; // num_samples * 8 = 5 * 8 = 40 + size_t num_random_offset_bytes = 0; + size_t num_samples = 5; + char *data = nullptr; +}; + +TEST_CASE_METHOD(DataProcessorTestFixture, "Remove Trailing Bits", + "[.dataprocessor][.bitoffset]") { + + const size_t num_random_offset_bytes = 3; + set_random_offset_bytes(num_random_offset_bytes); + set_data(); + + dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); + + size_t expected_size = get_size() - num_random_offset_bytes; + + char *expected_data = new char[expected_size]; + memset(expected_data, dummy_value, num_analog_bytes); // set to 125 + memset(expected_data + num_analog_bytes, 0xFF, + num_digital_bytes); // set to 1 + memset(expected_data + num_digital_bytes + num_analog_bytes, dummy_value, + num_transceiver_bytes); // set to 125 + + size_t size = get_size(); + dataprocessor->RemoveTrailingBits(size, data); + + CHECK(size == expected_size); + + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; } -// TEST_CASE("Arrange with reorder on") { +// parametric test tested with num_samples = 5, num_samples = 10, num_samples = +// 8 +TEST_CASE_METHOD(DataProcessorTestFixture, "Reorder all", + "[.dataprocessor][.reorder]") { + // parameters: num_samples, expected_num_digital_bytes, + // expected_digital_part + auto parameters = GENERATE( + std::make_tuple(5, 64, std::vector{0b00011111}), + std::make_tuple(10, 2 * 64, std::vector{0xFF, 0b00000011}), + std::make_tuple(8, 64, std::vector{0xFF})); -//} + size_t num_samples, expected_num_digital_bytes; + std::vector expected_digital_part; + std::tie(num_samples, expected_num_digital_bytes, expected_digital_part) = + parameters; -// test case reorder on and bitoffset set + // set number of samples for test fixture -> create data + set_num_samples(num_samples); + set_data(); -// test with different samples + dataprocessor->SetReorder(true); // set reorder to true -// test with sample number not divisable and ctbitlist not divisable + const size_t expected_size = + num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; + + // create expected data + char *expected_data = new char[expected_size]; + + memset(expected_data, dummy_value, num_analog_bytes); // set to 125 + for (size_t bit = 0; bit < 64; ++bit) { + memcpy(expected_data + num_analog_bytes + + expected_digital_part.size() * bit, + expected_digital_part.data(), expected_digital_part.size()); + } + memset(expected_data + expected_num_digital_bytes + num_analog_bytes, + dummy_value, + num_transceiver_bytes); // set to 125 + + size_t size = get_size(); + dataprocessor->Reorder(size, data); // call reorder + + CHECK(size == expected_size); + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; +} + +TEST_CASE_METHOD(DataProcessorTestFixture, + "Reorder all and remove trailing bits", + "[.dataprocessor][.reorder]") { + + // set number of samples for test fixture -> create data + const size_t num_random_offset_bytes = 3; + set_random_offset_bytes(num_random_offset_bytes); + set_data(); + + dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); + dataprocessor->SetReorder(true); // set reorder to true + + const size_t expected_num_digital_bytes = 64; + std::vector expected_digital_part{0b00011111}; + + const size_t expected_size = + num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; + + // create expected data + char *expected_data = new char[expected_size]; + + memset(expected_data, dummy_value, num_analog_bytes); // set to 125 + for (size_t bit = 0; bit < 64; ++bit) { + memcpy(expected_data + num_analog_bytes + + expected_digital_part.size() * bit, + expected_digital_part.data(), expected_digital_part.size()); + } + memset(expected_data + expected_num_digital_bytes + num_analog_bytes, + dummy_value, + num_transceiver_bytes); // set to 125 + + size_t size = get_size(); + dataprocessor->Reorder(size, data); // call reorder + + CHECK(size == expected_size); + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; +} + +TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder false", + "[.dataprocessor][.retrievebitlist]") { + // parameters: num_samples, bitlist, expected_num_digital_bytes, + // expected_digital_part + auto parameters = GENERATE( + std::make_tuple(5, std::vector{1, 4, 5}, 5, + std::vector{0b00000111}), + std::make_tuple(5, std::vector{1, 5, 3, 7, 8, 50, 42, 60, 39}, 10, + std::vector{0xFF, 0b00000001}), + std::make_tuple(5, std::vector{1, 5, 3, 7, 8, 50, 42, 60}, 5, + std::vector{0xFF})); + + size_t num_samples, expected_num_digital_bytes; + std::vector expected_digital_part; + std::vector bitlist; + std::tie(num_samples, bitlist, expected_num_digital_bytes, + expected_digital_part) = parameters; + + dataprocessor->SetCtbDbitList(bitlist); + + dataprocessor->SetReorder(false); + + set_num_samples(num_samples); + set_data(); + + size_t expected_size = + num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; + + // create expected data + char *expected_data = new char[expected_size]; + + memset(expected_data, dummy_value, num_analog_bytes); + + for (size_t sample = 0; sample < num_samples; ++sample) { + memcpy(expected_data + num_analog_bytes + + expected_digital_part.size() * sample, + expected_digital_part.data(), expected_digital_part.size()); + } + + memset(expected_data + expected_num_digital_bytes + num_analog_bytes, + dummy_value, num_transceiver_bytes); + + size_t size = get_size(); + dataprocessor->ArrangeDbitData(size, data); + + CHECK(size == expected_size); + + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; +} + +TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder true", + "[.dataprocessor][.retrievebitlist]") { + // parameters: num_samples, bitlist, expected_num_digital_bytes, + // expected_digital_part + auto parameters = GENERATE( + std::make_tuple(5, std::vector{1, 4, 5}, 3, + std::vector{0b00011111}), + std::make_tuple(10, std::vector{1, 4, 5}, 6, + std::vector{0xFF, 0b00000011}), + std::make_tuple(8, std::vector{1, 5, 3, 7, 8, 50, 42, 60, 39}, 9, + std::vector{0xFF})); + + size_t num_samples, expected_num_digital_bytes; + std::vector expected_digital_part; + std::vector bitlist; + std::tie(num_samples, bitlist, expected_num_digital_bytes, + expected_digital_part) = parameters; + + dataprocessor->SetCtbDbitList(bitlist); + + dataprocessor->SetReorder(true); + + set_num_samples(num_samples); + set_data(); + + size_t expected_size = + num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; + + // create expected data + char *expected_data = new char[expected_size]; + + memset(expected_data, dummy_value, num_analog_bytes); + + for (size_t sample = 0; sample < bitlist.size(); ++sample) { + memcpy(expected_data + num_analog_bytes + + expected_digital_part.size() * sample, + expected_digital_part.data(), expected_digital_part.size()); + } + + memset(expected_data + expected_num_digital_bytes + num_analog_bytes, + dummy_value, num_transceiver_bytes); + + size_t size = get_size(); + dataprocessor->ArrangeDbitData(size, data); + + CHECK(size == expected_size); + + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; +} + +TEST_CASE_METHOD(DataProcessorTestFixture, + "Arrange bitlist and remove trailing bits", + "[.dataprocessor][.retrievebitlist]") { + + size_t num_random_offset_bytes = 3; + std::vector bitlist{1, 4, 5}; + + set_random_offset_bytes(num_random_offset_bytes); + set_data(); + + dataprocessor->SetCtbDbitList(bitlist); + + dataprocessor->SetReorder(false); + + dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); + + std::vector expected_digital_part{0b00000111}; + const size_t expected_num_digital_bytes = 5; + + size_t expected_size = + num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; + + // create expected data + char *expected_data = new char[expected_size]; + + memset(expected_data, dummy_value, num_analog_bytes); + + for (size_t sample = 0; sample < num_samples; ++sample) { + memcpy(expected_data + num_analog_bytes + + expected_digital_part.size() * sample, + expected_digital_part.data(), expected_digital_part.size()); + } + + memset(expected_data + expected_num_digital_bytes + num_analog_bytes, + dummy_value, num_transceiver_bytes); + + size_t size = get_size(); + dataprocessor->ArrangeDbitData(size, data); + + CHECK(size == expected_size); + + CHECK(memcmp(data, expected_data, expected_size) == 0); + + delete[] expected_data; +} } // namespace sls From 13b2cada66491db82d2e3871ee314d26de207399 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 17 Mar 2025 15:20:40 +0100 Subject: [PATCH 012/103] ctb reorder default being true in dataprocessor --- slsReceiverSoftware/src/DataProcessor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 594cffaa8..fd5b9dc1f 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -181,7 +181,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { bool framePadding; std::vector ctbDbitList; int ctbDbitOffset{0}; - bool ctbDbitReorder{false}; + bool ctbDbitReorder{true}; std::atomic startedFlag{false}; std::atomic firstIndex{0}; bool quadEnable{false}; From 842b376801998d114b142c6e7a39202c84a3f187 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 17 Mar 2025 15:23:02 +0100 Subject: [PATCH 013/103] fixed rx_dbitreorder cmd line tests --- slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp index d04890b0a..508abc59d 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp @@ -963,11 +963,6 @@ TEST_CASE("rx_dbitreorder", "[.cmdcall][.rx]") { if (det_type == defs::CHIPTESTBOARD || det_type == defs::XILINX_CHIPTESTBOARD) { auto prev_val = det.getRxDbitReorder(); - { - std::ostringstream oss; - caller.call("rx_dbitreorder", {"1"}, -1, PUT, oss); - REQUIRE(oss.str() == "rx_dbitreorder 1\n"); - } { std::ostringstream oss; caller.call("rx_dbitreorder", {"0"}, -1, PUT, oss); @@ -988,7 +983,7 @@ TEST_CASE("rx_dbitreorder", "[.cmdcall][.rx]") { det.setRxDbitReorder(prev_val[i], {i}); } } else { - REQUIRE_THROWS(caller.call("rx_dbitoffset", {}, -1, GET)); + REQUIRE_THROWS(caller.call("rx_dbitreorder", {}, -1, GET)); } } From e0a48e1e7504b73f598c7b9451695c6c5d911b6a Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Mon, 17 Mar 2025 15:39:31 +0100 Subject: [PATCH 014/103] implemented proper alignment in reorder function before casting to uint64_t ptr --- slsReceiverSoftware/src/DataProcessor.cpp | 39 ++++++++++++++----- .../tests/test-ArrangeDataBasedOnBitList.cpp | 4 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 222917d4e..724c5758d 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -573,18 +573,35 @@ void DataProcessor::Reorder(size_t &size, char *data) { return; } - auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); - // TODO: leads to unaligned data + // make sure data is aligned to 8 bytes before casting to uint64_t + char *ptr = data + nAnalogDataBytes + ctbDbitOffset; + uint64_t *source = nullptr; + using AlignedBuffer = + std::aligned_storage::type; + std::unique_ptr tempbuffer; + + if (reinterpret_cast(ptr) % alignof(uint64_t) == 0) { + // If aligned, directly cast to uint64_t pointer + source = reinterpret_cast(ptr); + } else { + // Allocate a temporary buffer with proper alignment + tempbuffer = std::make_unique(ctbDigitalDataBytes / + sizeof(uint64_t)); + + std::memcpy(tempbuffer.get(), ptr, ctbDigitalDataBytes); + source = reinterpret_cast(tempbuffer.get()); + } + + // auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); + // TODO: leads to unaligned data const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); size_t numBytesPerBit = - 0; // number of bytes per bit in digital data after reordering - - if ((numDigitalSamples % 8) == 0) - numBytesPerBit = numDigitalSamples / 8; - else - numBytesPerBit = numDigitalSamples / 8 + 1; + (numDigitalSamples % 8 == 0) + ? numDigitalSamples / 8 + : numDigitalSamples / 8 + + 1; // number of bytes per bit in digital data after reordering size_t totalNumBytes = numBytesPerBit * @@ -595,10 +612,13 @@ void DataProcessor::Reorder(size_t &size, char *data) { int bitoffset = 0; // reorder + int count = 0; + int written = 0; for (size_t bi = 0; bi < 64; ++bi) { if (bitoffset != 0) { bitoffset = 0; + ++count; ++dest; } @@ -606,11 +626,12 @@ void DataProcessor::Reorder(size_t &size, char *data) { uint8_t bit = (*ptr >> bi) & 1; *dest |= bit << bitoffset; // most significant bits will be padded ++bitoffset; - if (bitoffset == 8) { bitoffset = 0; + ++count; ++dest; } + ++written; } } diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index ad5a8fbb3..1235daae1 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -39,8 +39,8 @@ class GeneralDataTest : public GeneralData { // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0){}; - ~DataProcessorTest(){}; + DataProcessorTest() : DataProcessor(0) {}; + ~DataProcessorTest() {}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } From f4626c2c815d8efb337224d97b59b335651aafe4 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 17 Mar 2025 15:45:59 +0100 Subject: [PATCH 015/103] fix tests from merge --- .../tests/test-ArrangeDataBasedOnBitList.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index ad5a8fbb3..e556066bc 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -39,8 +39,8 @@ class GeneralDataTest : public GeneralData { // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0){}; - ~DataProcessorTest(){}; + DataProcessorTest() : DataProcessor(0) {}; + ~DataProcessorTest() {}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } @@ -179,7 +179,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Reorder all", set_num_samples(num_samples); set_data(); - dataprocessor->SetReorder(true); // set reorder to true + dataprocessor->SetCtbDbitReorder(true); // set reorder to true const size_t expected_size = num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; @@ -216,7 +216,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, set_data(); dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); - dataprocessor->SetReorder(true); // set reorder to true + dataprocessor->SetCtbDbitReorder(true); // set reorder to true const size_t expected_num_digital_bytes = 64; std::vector expected_digital_part{0b00011111}; @@ -266,7 +266,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder false", dataprocessor->SetCtbDbitList(bitlist); - dataprocessor->SetReorder(false); + dataprocessor->SetCtbDbitReorder(false); set_num_samples(num_samples); set_data(); @@ -318,7 +318,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder true", dataprocessor->SetCtbDbitList(bitlist); - dataprocessor->SetReorder(true); + dataprocessor->SetCtbDbitReorder(true); set_num_samples(num_samples); set_data(); @@ -362,7 +362,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, dataprocessor->SetCtbDbitList(bitlist); - dataprocessor->SetReorder(false); + dataprocessor->SetCtbDbitReorder(false); dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); From f056f9d31b304dda9dff2d723c95cc5d512a53f3 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Tue, 18 Mar 2025 21:42:34 +0100 Subject: [PATCH 016/103] reserved enough size in the fifo buffer to reorder all added proper 4 byte alignment --- slsReceiverSoftware/src/DataProcessor.cpp | 89 ++++++++++++------- slsReceiverSoftware/src/DataProcessor.h | 2 +- slsReceiverSoftware/src/GeneralData.h | 14 ++- .../tests/test-ArrangeDataBasedOnBitList.cpp | 6 +- 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 724c5758d..bd89784ff 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -26,6 +26,49 @@ namespace sls { +// TODO: move somewhere else +template struct AlignedData { + T *ptr; // Aligned data pointer + std::unique_ptr[]> buffer; + + AlignedData( + T *p, + std::unique_ptr[]> buf) + : ptr(p), buffer(std::move(buf)) {} +}; + +// TODO: should not be in this file any suggestions to move it to a more +// appropriate file? +// TODO: Add unit test +/* + * AlignData + * Aligns data to a given type T with proper alignment + * @param data: pointer to data + * @param size: size of data to align in bytes + * @return: aligned data + */ +template +void AlignData(AlignedData &aligneddata, char *data, size_t size) { + using AlignedBuffer = + typename std::aligned_storage::type; + std::unique_ptr tempbuffer; + + if (reinterpret_cast(data) % alignof(uint64_t) == 0) { + // If aligned directly cast to pointer + + aligneddata.ptr = reinterpret_cast(data); + + } else { + // Allocate a temporary buffer with proper alignment + tempbuffer = std::make_unique(size / sizeof(T)); + // size = ctbDigitaldbt; + + std::memcpy(tempbuffer.get(), data, size); + aligneddata.buffer = std::move(tempbuffer); + aligneddata.ptr = reinterpret_cast(aligneddata.buffer.get()); + } +} + const std::string DataProcessor::typeName = "DataProcessor"; DataProcessor::DataProcessor(int index) : ThreadObject(index, typeName) { @@ -574,26 +617,11 @@ void DataProcessor::Reorder(size_t &size, char *data) { } // make sure data is aligned to 8 bytes before casting to uint64_t - char *ptr = data + nAnalogDataBytes + ctbDbitOffset; - uint64_t *source = nullptr; - using AlignedBuffer = - std::aligned_storage::type; - std::unique_ptr tempbuffer; + AlignedData aligned_data(nullptr, nullptr); + AlignData(aligned_data, data + nAnalogDataBytes + ctbDbitOffset, + ctbDigitalDataBytes); - if (reinterpret_cast(ptr) % alignof(uint64_t) == 0) { - // If aligned, directly cast to uint64_t pointer - source = reinterpret_cast(ptr); - } else { - // Allocate a temporary buffer with proper alignment - tempbuffer = std::make_unique(ctbDigitalDataBytes / - sizeof(uint64_t)); - - std::memcpy(tempbuffer.get(), ptr, ctbDigitalDataBytes); - source = reinterpret_cast(tempbuffer.get()); - } - - // auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); - // TODO: leads to unaligned data + uint64_t *source = aligned_data.ptr; const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -612,13 +640,10 @@ void DataProcessor::Reorder(size_t &size, char *data) { int bitoffset = 0; // reorder - int count = 0; - int written = 0; for (size_t bi = 0; bi < 64; ++bi) { if (bitoffset != 0) { bitoffset = 0; - ++count; ++dest; } @@ -628,10 +653,8 @@ void DataProcessor::Reorder(size_t &size, char *data) { ++bitoffset; if (bitoffset == 8) { bitoffset = 0; - ++count; ++dest; } - ++written; } } @@ -671,7 +694,13 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { return; } - auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); + AlignedData aligned_data(nullptr, nullptr); + AlignData(aligned_data, data + nAnalogDataBytes + ctbDbitOffset, + ctbDigitalDataBytes); + + uint64_t *source = aligned_data.ptr; + + // auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -747,19 +776,19 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { } } - // copy back to memory and update size - memcpy(data + nAnalogDataBytes, result.data(), - totalNumBytes * sizeof(uint8_t)); - size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + nTransceiverDataBytes; // check if size changed, if so move transceiver data to avoid gap in memory - if (size < nAnalogDataBytes + nDigitalDataBytes + nTransceiverDataBytes) + if (size != nAnalogDataBytes + nDigitalDataBytes + nTransceiverDataBytes) memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), data + nAnalogDataBytes + nDigitalDataBytes, nTransceiverDataBytes); + // copy back to memory and update size + memcpy(data + nAnalogDataBytes, result.data(), + totalNumBytes * sizeof(uint8_t)); + LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes << " nAnalogDataBytes:" << nAnalogDataBytes << " ctbDbitOffset:" << ctbDbitOffset diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 3e0aa0b9d..ab58b2fe6 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -177,7 +177,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin {}; + struct timespec timerbegin{}; bool framePadding; std::vector ctbDbitList; bool reorder{false}; // true if data should be reordered TODO: add as mode diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 0425815a3..6d9a5d626 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -60,8 +60,8 @@ class GeneralData { slsDetectorDefs::frameDiscardPolicy frameDiscardMode{ slsDetectorDefs::NO_DISCARD}; - GeneralData(){}; - virtual ~GeneralData(){}; + GeneralData() {}; + virtual ~GeneralData() {}; // Returns the pixel depth in byte, 4 bits being 0.5 byte float GetPixelDepth() { return float(dynamicRange) / 8; } @@ -443,6 +443,7 @@ class ChipTestBoardData : public GeneralData { nDigitalBytes = 0; nTransceiverBytes = 0; int nAnalogChans = 0, nDigitalChans = 0, nTransceiverChans = 0; + uint64_t digital_bytes_reserved = 0; // analog channels (normal, analog/digital readout) if (readoutType == slsDetectorDefs::ANALOG_ONLY || @@ -461,7 +462,12 @@ class ChipTestBoardData : public GeneralData { readoutType == slsDetectorDefs::ANALOG_AND_DIGITAL || readoutType == slsDetectorDefs::DIGITAL_AND_TRANSCEIVER) { nDigitalChans = NCHAN_DIGITAL; - nDigitalBytes = (sizeof(uint64_t) * nDigitalSamples); + // allocate enough memory to support reordering of digital bits + uint32_t num_bytes_per_bit = (nDigitalSamples % 8 == 0) + ? nDigitalSamples / 8 + : nDigitalSamples / 8 + 1; + digital_bytes_reserved = 64 * num_bytes_per_bit; + nDigitalBytes = sizeof(uint64_t) * nDigitalSamples; LOG(logDEBUG1) << "Number of Digital Channels:" << nDigitalChans << " Databytes: " << nDigitalBytes; } @@ -480,7 +486,7 @@ class ChipTestBoardData : public GeneralData { nPixelsX = nAnalogChans + nDigitalChans + nTransceiverChans; dataSize = tengigaEnable ? 8144 : UDP_PACKET_DATA_BYTES; packetSize = headerSizeinPacket + dataSize; - imageSize = nAnalogBytes + nDigitalBytes + nTransceiverBytes; + imageSize = nAnalogBytes + digital_bytes_reserved + nTransceiverBytes; packetsPerFrame = ceil((double)imageSize / (double)dataSize); LOG(logDEBUG1) << "Total Number of Channels:" << nPixelsX diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index 1235daae1..3139e11e6 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -106,7 +106,11 @@ class DataProcessorTestFixture { void set_data() { delete[] data; - data = new char[get_size()]; + uint64_t max_bytes_per_bit = + num_samples % 8 == 0 ? num_samples / 8 : num_samples / 8 + 1; + uint64_t reserved_size = + get_size() - num_digital_bytes + max_bytes_per_bit * 64; + data = new char[reserved_size]; // set testing data memset(data, dummy_value, num_analog_bytes); // set to dummy value From c54b1cb6afe3182a8dff59f4b64c40b7a66436c9 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 19 Mar 2025 08:37:07 +0100 Subject: [PATCH 017/103] clang format --- slsReceiverSoftware/src/DataProcessor.h | 2 +- slsReceiverSoftware/src/GeneralData.h | 4 ++-- slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index fd5b9dc1f..eec4df506 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -177,7 +177,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin{}; + struct timespec timerbegin {}; bool framePadding; std::vector ctbDbitList; int ctbDbitOffset{0}; diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 6d9a5d626..9f3b43429 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -60,8 +60,8 @@ class GeneralData { slsDetectorDefs::frameDiscardPolicy frameDiscardMode{ slsDetectorDefs::NO_DISCARD}; - GeneralData() {}; - virtual ~GeneralData() {}; + GeneralData(){}; + virtual ~GeneralData(){}; // Returns the pixel depth in byte, 4 bits being 0.5 byte float GetPixelDepth() { return float(dynamicRange) / 8; } diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index c651de594..af75c3b54 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -39,8 +39,8 @@ class GeneralDataTest : public GeneralData { // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0) {}; - ~DataProcessorTest() {}; + DataProcessorTest() : DataProcessor(0){}; + ~DataProcessorTest(){}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } From b7e17d1320b3cc48c5b56f3671cc65f7a1f473d9 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 19 Mar 2025 11:51:28 +0100 Subject: [PATCH 018/103] added reorder to documentation, added flag to master binary and hdf5 file --- docs/src/binaryfileformat.rst | 1 + docs/src/masterfileattributes.rst | 3 +++ slsReceiverSoftware/src/ClientInterface.cpp | 2 +- slsReceiverSoftware/src/DataProcessor.h | 2 +- slsReceiverSoftware/src/Implementation.cpp | 2 ++ slsReceiverSoftware/src/MasterAttributes.cpp | 13 +++++++++++++ slsReceiverSoftware/src/MasterAttributes.h | 2 ++ slsReceiverSoftware/src/receiver_defs.h | 4 ++-- 8 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/src/binaryfileformat.rst b/docs/src/binaryfileformat.rst index 42b2a5657..e3657f246 100644 --- a/docs/src/binaryfileformat.rst +++ b/docs/src/binaryfileformat.rst @@ -359,6 +359,7 @@ Chip Test Board "Digital Flag": 0, "Digital Samples": 1000, "Dbit Offset": 0, + "Dbit Reorder": 1, "Dbit Bitset": 0, "Transceiver Mask": "0x3", "Transceiver Flag": 0, diff --git a/docs/src/masterfileattributes.rst b/docs/src/masterfileattributes.rst index cc1355553..777c930ff 100644 --- a/docs/src/masterfileattributes.rst +++ b/docs/src/masterfileattributes.rst @@ -345,6 +345,9 @@ Chip Test Board +-----------------------+-------------------------------------------------+ | Dbit Offset | Digital offset of valid data in bytes | +-----------------------+-------------------------------------------------+ + | Dbit Reorder | Group bits of all samples together to have them | + | | contiguous in the file | + +-----------------------+-------------------------------------------------+ | Dbit Bitset | Digital 64 bit mask of bits enabled in receiver | +-----------------------+-------------------------------------------------+ | Transceiver Mask | Mask of channels enabled in Transceiver | diff --git a/slsReceiverSoftware/src/ClientInterface.cpp b/slsReceiverSoftware/src/ClientInterface.cpp index 0180af8fb..f3de129b5 100644 --- a/slsReceiverSoftware/src/ClientInterface.cpp +++ b/slsReceiverSoftware/src/ClientInterface.cpp @@ -1807,7 +1807,7 @@ int ClientInterface::set_dbit_reorder(Interface &socket) { throw RuntimeError("Invalid dbit reorder: " + std::to_string(arg)); } verifyIdle(socket); - LOG(logDEBUG1) << "Setting Dbit offset: " << arg; + LOG(logDEBUG1) << "Setting Dbit reorder: " << arg; impl()->setDbitReorder(arg); return socket.Send(OK); } diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index eec4df506..fd5b9dc1f 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -177,7 +177,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin {}; + struct timespec timerbegin{}; bool framePadding; std::vector ctbDbitList; int ctbDbitOffset{0}; diff --git a/slsReceiverSoftware/src/Implementation.cpp b/slsReceiverSoftware/src/Implementation.cpp index ea636258c..e4385d9c0 100644 --- a/slsReceiverSoftware/src/Implementation.cpp +++ b/slsReceiverSoftware/src/Implementation.cpp @@ -992,7 +992,9 @@ void Implementation::StartMasterWriter() { : 0; masterAttributes.digitalSamples = generalData->nDigitalSamples; masterAttributes.dbitoffset = ctbDbitOffset; + masterAttributes.dbitreorder = ctbDbitReorder; masterAttributes.dbitlist = 0; + for (auto &i : ctbDbitList) { masterAttributes.dbitlist |= (static_cast(1) << i); } diff --git a/slsReceiverSoftware/src/MasterAttributes.cpp b/slsReceiverSoftware/src/MasterAttributes.cpp index dd85a0381..8420e31ad 100644 --- a/slsReceiverSoftware/src/MasterAttributes.cpp +++ b/slsReceiverSoftware/src/MasterAttributes.cpp @@ -551,6 +551,13 @@ void MasterAttributes::WriteHDF5DbitOffset(H5::H5File *fd, H5::Group *group) { dataset.write(&dbitoffset, H5::PredType::NATIVE_INT); } +void MasterAttributes::WriteHDF5DbitReorder(H5::H5File *fd, H5::Group *group) { + H5::DataSpace dataspace = H5::DataSpace(H5S_SCALAR); + H5::DataSet dataset = group->createDataSet( + "Dbit Reorder", H5::PredType::NATIVE_INT, dataspace); + dataset.write(&dbitreorder, H5::PredType::NATIVE_INT); +} + void MasterAttributes::WriteHDF5DbitList(H5::H5File *fd, H5::Group *group) { H5::DataSpace dataspace = H5::DataSpace(H5S_SCALAR); H5::DataSet dataset = group->createDataSet( @@ -744,6 +751,8 @@ void MasterAttributes::GetCtbBinaryAttributes( w->Uint(digitalSamples); w->Key("Dbit Offset"); w->Uint(dbitoffset); + w->Key("Dbit Reorder"); + w->Uint(dbitreorder); w->Key("Dbit Bitset"); w->Uint64(dbitlist); w->Key("Transceiver Mask"); @@ -766,6 +775,7 @@ void MasterAttributes::WriteCtbHDF5Attributes(H5::H5File *fd, MasterAttributes::WriteHDF5DigitalFlag(fd, group); MasterAttributes::WriteHDF5DigitalSamples(fd, group); MasterAttributes::WriteHDF5DbitOffset(fd, group); + MasterAttributes::WriteHDF5DbitReorder(fd, group); MasterAttributes::WriteHDF5DbitList(fd, group); MasterAttributes::WriteHDF5TransceiverMask(fd, group); MasterAttributes::WriteHDF5TransceiverFlag(fd, group); @@ -791,6 +801,8 @@ void MasterAttributes::GetXilinxCtbBinaryAttributes( w->Uint(digitalSamples); w->Key("Dbit Offset"); w->Uint(dbitoffset); + w->Key("Dbit Reorder"); + w->Uint(dbitreorder); w->Key("Dbit Bitset"); w->Uint64(dbitlist); w->Key("Transceiver Mask"); @@ -812,6 +824,7 @@ void MasterAttributes::WriteXilinxCtbHDF5Attributes(H5::H5File *fd, MasterAttributes::WriteHDF5DigitalFlag(fd, group); MasterAttributes::WriteHDF5DigitalSamples(fd, group); MasterAttributes::WriteHDF5DbitOffset(fd, group); + MasterAttributes::WriteHDF5DbitReorder(fd, group); MasterAttributes::WriteHDF5DbitList(fd, group); MasterAttributes::WriteHDF5TransceiverMask(fd, group); MasterAttributes::WriteHDF5TransceiverFlag(fd, group); diff --git a/slsReceiverSoftware/src/MasterAttributes.h b/slsReceiverSoftware/src/MasterAttributes.h index 015c632c4..0e3c168ae 100644 --- a/slsReceiverSoftware/src/MasterAttributes.h +++ b/slsReceiverSoftware/src/MasterAttributes.h @@ -51,6 +51,7 @@ class MasterAttributes { uint32_t analogSamples{0}; uint32_t digital{0}; uint32_t digitalSamples{0}; + uint32_t dbitreorder{1}; uint32_t dbitoffset{0}; uint64_t dbitlist{0}; uint32_t transceiverMask{0}; @@ -104,6 +105,7 @@ class MasterAttributes { void WriteHDF5DigitalSamples(H5::H5File *fd, H5::Group *group); void WriteHDF5DbitOffset(H5::H5File *fd, H5::Group *group); void WriteHDF5DbitList(H5::H5File *fd, H5::Group *group); + void WriteHDF5DbitReorder(H5::H5File *fd, H5::Group *group); void WriteHDF5TransceiverMask(H5::H5File *fd, H5::Group *group); void WriteHDF5TransceiverFlag(H5::H5File *fd, H5::Group *group); void WriteHDF5TransceiverSamples(H5::H5File *fd, H5::Group *group); diff --git a/slsReceiverSoftware/src/receiver_defs.h b/slsReceiverSoftware/src/receiver_defs.h index ba9697706..ade363e79 100644 --- a/slsReceiverSoftware/src/receiver_defs.h +++ b/slsReceiverSoftware/src/receiver_defs.h @@ -19,8 +19,8 @@ namespace sls { // files // versions -#define HDF5_WRITER_VERSION (6.6) // 1 decimal places -#define BINARY_WRITER_VERSION (7.2) // 1 decimal places +#define HDF5_WRITER_VERSION (6.7) // 1 decimal places +#define BINARY_WRITER_VERSION (7.3) // 1 decimal places #define MAX_FRAMES_PER_FILE 20000 #define SHORT_MAX_FRAMES_PER_FILE 100000 From 8b3625fc011ac4694d959fefae24225942329088 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Thu, 20 Mar 2025 11:00:40 +0100 Subject: [PATCH 019/103] added dbitreorder flag to chip test board gui --- pyctbgui/pyctbgui/services/Signals.py | 118 ++++++++++++++++---------- pyctbgui/pyctbgui/ui/signals.ui | 102 +++++++++++++--------- 2 files changed, 135 insertions(+), 85 deletions(-) diff --git a/pyctbgui/pyctbgui/services/Signals.py b/pyctbgui/pyctbgui/services/Signals.py index a06b9a412..edd8d7e05 100644 --- a/pyctbgui/pyctbgui/services/Signals.py +++ b/pyctbgui/pyctbgui/services/Signals.py @@ -22,6 +22,7 @@ class SignalsTab(QtWidgets.QWidget): self.plotTab = None self.legend: LegendItem | None = None self.rx_dbitoffset = None + self.rx_dbitreorder = None self.rx_dbitlist = None def refresh(self): @@ -29,6 +30,7 @@ class SignalsTab(QtWidgets.QWidget): self.updateDigitalBitEnable() self.updateIOOut() self.getDBitOffset() + self.getDBitReorder() def connect_ui(self): for i in range(Defines.signals.count): @@ -49,6 +51,7 @@ class SignalsTab(QtWidgets.QWidget): partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count)) self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg) self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + self.view.checkBoxDBitReorder.stateChanged.connect(self.setDbitReorder) def setup_ui(self): self.plotTab = self.mainWindow.plotTab @@ -87,60 +90,74 @@ class SignalsTab(QtWidgets.QWidget): self.legend.addItem(plot, name) @recordOrApplyPedestal - def _processWaveformData(self, data, aSamples, dSamples, rx_dbitlist, isPlottedArray, rx_dbitoffset, romode, + def _processWaveformData(self, data, aSamples, dSamples, rx_reorder, rx_dbitlist, isPlottedArray, romode, nADCEnabled): - """ - transform raw waveform data into a processed numpy array - @param data: raw waveform data - """ - dbitoffset = rx_dbitoffset + + #transform raw waveform data into a processed numpy array + #@param data: raw waveform data + + start_digital_data = 0 if romode == 2: - dbitoffset += nADCEnabled * 2 * aSamples - digital_array = np.array(np.frombuffer(data, offset=dbitoffset, dtype=np.uint8)) - nbitsPerDBit = dSamples - if nbitsPerDBit % 8 != 0: - nbitsPerDBit += (8 - (dSamples % 8)) - offset = 0 - arr = [] - for i in rx_dbitlist: - # where numbits * numsamples is not a multiple of 8 - if offset % 8 != 0: - offset += (8 - (offset % 8)) - if not isPlottedArray[i]: - offset += nbitsPerDBit - return None - waveform = np.zeros(dSamples) - for iSample in range(dSamples): - # all samples for digital bit together from slsReceiver - index = int(offset / 8) - iBit = offset % 8 - bit = (digital_array[index] >> iBit) & 1 - waveform[iSample] = bit - offset += 1 - arr.append(waveform) - - return np.array(arr) - + start_digital_data += nADCEnabled * 2 * aSamples + digital_array = np.array(np.frombuffer(data, offset=start_digital_data, dtype=np.uint8)) + if rx_reorder: + nbitsPerDBit = dSamples + arr = np.empty((rx_dbitlist.size, dSamples), dtype=np.uint8) + if nbitsPerDBit % 8 != 0: + nbitsPerDBit += (8 - (dSamples % 8)) + offset = 0 + for i in rx_dbitlist: + # where numbits * numsamples is not a multiple of 8 + if offset % 8 != 0: + offset += (8 - (offset % 8)) + if not isPlottedArray[i]: + offset += nbitsPerDBit + arr[i, :] = np.nan + continue + for iSample in range(dSamples): + # all samples for digital bit together from slsReceiver + index = int(offset / 8) + iBit = offset % 8 + bit = (digital_array[index] >> iBit) & 1 + arr[i,iSample] = bit + offset += 1 + return arr + else: + nbitsPerSample = int(rx_dbitlist.size) if rx_dbitlist.size % 8 == 0 else int(rx_dbitlist.size + (8 - (rx_dbitlist.size % 8))) + arr = np.empty((dSamples, rx_dbitlist.size), dtype=np.uint8) #store all samples + for iSample in range(dSamples): + offset = nbitsPerSample * iSample + for i in range(rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! + if not isPlottedArray[i]: + offset += 1 + arr[iSample, i] = np.nan + index = int(offset/8) + iBit = i % 8 + bit = (digital_array[index] >> iBit) & 1 + arr[iSample, i] = bit + offset += 1 + return arr.T.copy() + def processWaveformData(self, data, aSamples, dSamples): - """ - view function - plots processed waveform data - data: raw waveform data - dsamples: digital samples - asamples: analog samples - """ + + #view function + #plots processed waveform data + #data: raw waveform data + #dsamples: digital samples + #asamples: analog samples + waveforms = {} isPlottedArray = {i: getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in self.rx_dbitlist} - digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitlist, isPlottedArray, - self.rx_dbitoffset, self.mainWindow.romode.value, + digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitreorder, self.rx_dbitlist, isPlottedArray, + self.mainWindow.romode.value, self.mainWindow.nADCEnabled) irow = 0 - for idx, i in enumerate(self.rx_dbitlist): + for idx, i in enumerate(self.rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! # bits enabled but not plotting - waveform = digital_array[idx] - if waveform is None: + waveform = digital_array[idx, :] + if np.isnan(waveform[0]): continue self.mainWindow.digitalPlots[i].setData(waveform) plotName = getattr(self.view, f"labelBIT{i}").text() @@ -151,8 +168,10 @@ class SignalsTab(QtWidgets.QWidget): irow += 1 else: self.mainWindow.digitalPlots[i].setY(0) + return waveforms + def initializeAllDigitalPlots(self): self.mainWindow.plotDigitalWaveform = pg.plot() self.mainWindow.plotDigitalWaveform.addLegend(colCount=Defines.colCount) @@ -360,14 +379,25 @@ class SignalsTab(QtWidgets.QWidget): self.view.spinBoxDBitOffset.setValue(self.rx_dbitoffset) self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + def getDBitReorder(self): + self.view.checkBoxDBitReorder.stateChanged.disconnect() + self.rx_dbitreorder = self.det.rx_dbitreorder + self.view.checkBoxDBitReorder.setChecked(self.rx_dbitreorder) + self.view.checkBoxDBitReorder.stateChanged.connect(self.setDbitReorder) + + def setDbitOffset(self): self.det.rx_dbitoffset = self.view.spinBoxDBitOffset.value() + def setDbitReorder(self): + self.det.rx_dbitreorder = self.view.checkBoxDBitReorder.isChecked() + def saveParameters(self) -> list: commands = [] dblist = [str(i) for i in range(Defines.signals.count) if getattr(self.view, f"checkBoxBIT{i}DB").isChecked()] if len(dblist) > 0: commands.append(f"rx_dbitlist {', '.join(dblist)}") commands.append(f"rx_dbitoffset {self.view.spinBoxDBitOffset.value()}") + commands.append(f"rx_dbitreorder {self.view.checkBoxDBitReorder.isChecked()}") commands.append(f"patioctrl {self.view.lineEditPatIOCtrl.text()}") return commands diff --git a/pyctbgui/pyctbgui/ui/signals.ui b/pyctbgui/pyctbgui/ui/signals.ui index 231c0b92f..d3f1022e9 100644 --- a/pyctbgui/pyctbgui/ui/signals.ui +++ b/pyctbgui/pyctbgui/ui/signals.ui @@ -6074,6 +6074,33 @@ QFrame::Raised + + + + + 150 + 32 + + + + + 150 + 32 + + + + + 10 + + + + background-color: rgb(255, 255, 255); + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -6086,6 +6113,31 @@ + + + + DBit Reorder + + + + + + + + 50 + 0 + + + + + 10 + + + + IO Control Register: + + + @@ -6133,50 +6185,18 @@ - - - + + + + Qt::Horizontal + + - 150 - 32 + 40 + 20 - - - 150 - 32 - - - - - 10 - - - - background-color: rgb(255, 255, 255); - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 50 - 0 - - - - - 10 - - - - IO Control Register: - - + From 713e4f6822283fe26ae6a664b5e0c4f56a5701fc Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Thu, 20 Mar 2025 11:00:40 +0100 Subject: [PATCH 020/103] added dbitreorder flag to chip test board gui --- pyctbgui/pyctbgui/services/Signals.py | 118 +++++++++++------- pyctbgui/pyctbgui/ui/signals.ui | 102 +++++++++------ .../slsDetectorFunctionList.c | 8 +- 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/pyctbgui/pyctbgui/services/Signals.py b/pyctbgui/pyctbgui/services/Signals.py index a06b9a412..d03cdc373 100644 --- a/pyctbgui/pyctbgui/services/Signals.py +++ b/pyctbgui/pyctbgui/services/Signals.py @@ -22,6 +22,7 @@ class SignalsTab(QtWidgets.QWidget): self.plotTab = None self.legend: LegendItem | None = None self.rx_dbitoffset = None + self.rx_dbitreorder = None self.rx_dbitlist = None def refresh(self): @@ -29,6 +30,7 @@ class SignalsTab(QtWidgets.QWidget): self.updateDigitalBitEnable() self.updateIOOut() self.getDBitOffset() + self.getDBitReorder() def connect_ui(self): for i in range(Defines.signals.count): @@ -49,6 +51,7 @@ class SignalsTab(QtWidgets.QWidget): partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count)) self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg) self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + self.view.checkBoxDBitReorder.stateChanged.connect(self.setDbitReorder) def setup_ui(self): self.plotTab = self.mainWindow.plotTab @@ -87,60 +90,74 @@ class SignalsTab(QtWidgets.QWidget): self.legend.addItem(plot, name) @recordOrApplyPedestal - def _processWaveformData(self, data, aSamples, dSamples, rx_dbitlist, isPlottedArray, rx_dbitoffset, romode, + def _processWaveformData(self, data, aSamples, dSamples, rx_reorder, rx_dbitlist, isPlottedArray, romode, nADCEnabled): - """ - transform raw waveform data into a processed numpy array - @param data: raw waveform data - """ - dbitoffset = rx_dbitoffset + + #transform raw waveform data into a processed numpy array + #@param data: raw waveform data + + start_digital_data = 0 if romode == 2: - dbitoffset += nADCEnabled * 2 * aSamples - digital_array = np.array(np.frombuffer(data, offset=dbitoffset, dtype=np.uint8)) - nbitsPerDBit = dSamples - if nbitsPerDBit % 8 != 0: - nbitsPerDBit += (8 - (dSamples % 8)) - offset = 0 - arr = [] - for i in rx_dbitlist: - # where numbits * numsamples is not a multiple of 8 - if offset % 8 != 0: - offset += (8 - (offset % 8)) - if not isPlottedArray[i]: - offset += nbitsPerDBit - return None - waveform = np.zeros(dSamples) - for iSample in range(dSamples): - # all samples for digital bit together from slsReceiver - index = int(offset / 8) - iBit = offset % 8 - bit = (digital_array[index] >> iBit) & 1 - waveform[iSample] = bit - offset += 1 - arr.append(waveform) - - return np.array(arr) - + start_digital_data += nADCEnabled * 2 * aSamples + digital_array = np.array(np.frombuffer(data, offset=start_digital_data, dtype=np.uint8)) + if rx_reorder: + arr = np.empty((len(rx_dbitlist), dSamples), dtype=np.uint8) + nbitsPerDBit = dSamples + if nbitsPerDBit % 8 != 0: + nbitsPerDBit += (8 - (dSamples % 8)) + offset = 0 + for idx, i in enumerate(rx_dbitlist): + # where numbits * numsamples is not a multiple of 8 + if offset % 8 != 0: + offset += (8 - (offset % 8)) + if not isPlottedArray[i]: + offset += nbitsPerDBit + arr[idx, :] = np.nan + continue + for iSample in range(dSamples): + # all samples for digital bit together from slsReceiver + index = int(offset / 8) + iBit = offset % 8 + bit = (digital_array[index] >> iBit) & 1 + arr[idx,iSample] = bit + offset += 1 + return arr + else: + nbitsPerSample = len(rx_dbitlist) if len(rx_dbitlist) % 8 == 0 else len(rx_dbitlist) + (8 - (len(rx_dbitlist) % 8)) + arr = np.empty((dSamples, len(rx_dbitlist)), dtype=np.uint8) #store all samples + for iSample in range(dSamples): + offset = nbitsPerSample * iSample + for idx, i in enumerate(rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! + if not isPlottedArray[i]: + offset += 1 + arr[iSample, idx] = np.nan + index = int(offset/8) + iBit = idx % 8 + bit = (digital_array[index] >> iBit) & 1 + arr[iSample, idx] = bit + offset += 1 + return arr.T.copy() + def processWaveformData(self, data, aSamples, dSamples): - """ - view function - plots processed waveform data - data: raw waveform data - dsamples: digital samples - asamples: analog samples - """ + + #view function + #plots processed waveform data + #data: raw waveform data + #dsamples: digital samples + #asamples: analog samples + waveforms = {} isPlottedArray = {i: getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in self.rx_dbitlist} - digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitlist, isPlottedArray, - self.rx_dbitoffset, self.mainWindow.romode.value, + digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitreorder, self.rx_dbitlist, isPlottedArray, + self.mainWindow.romode.value, self.mainWindow.nADCEnabled) irow = 0 - for idx, i in enumerate(self.rx_dbitlist): + for idx, i in enumerate(self.rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! # bits enabled but not plotting - waveform = digital_array[idx] - if waveform is None: + waveform = digital_array[idx, :] + if np.isnan(waveform[0]): continue self.mainWindow.digitalPlots[i].setData(waveform) plotName = getattr(self.view, f"labelBIT{i}").text() @@ -151,8 +168,10 @@ class SignalsTab(QtWidgets.QWidget): irow += 1 else: self.mainWindow.digitalPlots[i].setY(0) + return waveforms + def initializeAllDigitalPlots(self): self.mainWindow.plotDigitalWaveform = pg.plot() self.mainWindow.plotDigitalWaveform.addLegend(colCount=Defines.colCount) @@ -360,14 +379,25 @@ class SignalsTab(QtWidgets.QWidget): self.view.spinBoxDBitOffset.setValue(self.rx_dbitoffset) self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + def getDBitReorder(self): + self.view.checkBoxDBitReorder.stateChanged.disconnect() + self.rx_dbitreorder = self.det.rx_dbitreorder + self.view.checkBoxDBitReorder.setChecked(self.rx_dbitreorder) + self.view.checkBoxDBitReorder.stateChanged.connect(self.setDbitReorder) + + def setDbitOffset(self): self.det.rx_dbitoffset = self.view.spinBoxDBitOffset.value() + def setDbitReorder(self): + self.det.rx_dbitreorder = self.view.checkBoxDBitReorder.isChecked() + def saveParameters(self) -> list: commands = [] dblist = [str(i) for i in range(Defines.signals.count) if getattr(self.view, f"checkBoxBIT{i}DB").isChecked()] if len(dblist) > 0: commands.append(f"rx_dbitlist {', '.join(dblist)}") commands.append(f"rx_dbitoffset {self.view.spinBoxDBitOffset.value()}") + commands.append(f"rx_dbitreorder {self.view.checkBoxDBitReorder.isChecked()}") commands.append(f"patioctrl {self.view.lineEditPatIOCtrl.text()}") return commands diff --git a/pyctbgui/pyctbgui/ui/signals.ui b/pyctbgui/pyctbgui/ui/signals.ui index 231c0b92f..d3f1022e9 100644 --- a/pyctbgui/pyctbgui/ui/signals.ui +++ b/pyctbgui/pyctbgui/ui/signals.ui @@ -6074,6 +6074,33 @@ QFrame::Raised + + + + + 150 + 32 + + + + + 150 + 32 + + + + + 10 + + + + background-color: rgb(255, 255, 255); + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -6086,6 +6113,31 @@ + + + + DBit Reorder + + + + + + + + 50 + 0 + + + + + 10 + + + + IO Control Register: + + + @@ -6133,50 +6185,18 @@ - - - + + + + Qt::Horizontal + + - 150 - 32 + 40 + 20 - - - 150 - 32 - - - - - 10 - - - - background-color: rgb(255, 255, 255); - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 50 - 0 - - - - - 10 - - - - IO Control Register: - - + diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index bf9773e71..f1f33cb84 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -2258,11 +2258,17 @@ void *start_timer(void *arg) { int packetsPerFrame = ceil((double)imageSize / (double)dataSize); // Generate Data - char imageData[imageSize]; + char imageData[imageSize]; // memset(imageData, 0, imageSize); + /* for (int i = 0; i < imageSize; i += sizeof(uint16_t)) { *((uint16_t *)(imageData + i)) = i; } + */ + + for (int i = 0; i < imageSize; i += 2 * sizeof(uint64_t)) { + *((uint64_t *)(imageData + i)) = 0xffffffffffffffff; + } // Send data uint64_t frameNr = 0; From 96ae1a1cca865ab0e7083cd7ccafa43de1d2b53a Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Thu, 20 Mar 2025 16:47:42 +0100 Subject: [PATCH 021/103] found bug needed to refresh member variables --- pyctbgui/pyctbgui/services/Signals.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyctbgui/pyctbgui/services/Signals.py b/pyctbgui/pyctbgui/services/Signals.py index b48581a68..4f27ab886 100644 --- a/pyctbgui/pyctbgui/services/Signals.py +++ b/pyctbgui/pyctbgui/services/Signals.py @@ -113,7 +113,6 @@ class SignalsTab(QtWidgets.QWidget): if not isPlottedArray[i]: offset += nbitsPerDBit arr[idx, :] = np.nan - continue for iSample in range(dSamples): # all samples for digital bit together from slsReceiver @@ -128,10 +127,11 @@ class SignalsTab(QtWidgets.QWidget): arr = np.empty((dSamples, len(rx_dbitlist)), dtype=np.uint8) #store all samples for iSample in range(dSamples): offset = nbitsPerSample * iSample - for idx, i in enumerate(rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! + for idx, i in enumerate(rx_dbitlist): if not isPlottedArray[i]: offset += 1 arr[iSample, idx] = np.nan + index = int(offset/8) iBit = idx % 8 bit = (digital_array[index] >> iBit) & 1 @@ -146,6 +146,8 @@ class SignalsTab(QtWidgets.QWidget): #data: raw waveform data #dsamples: digital samples #asamples: analog samples + + self.refresh() waveforms = {} isPlottedArray = {i: getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in self.rx_dbitlist} From 1d1b55b864e24eb3e75b2f13c4c205f700e6db57 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Tue, 8 Apr 2025 15:57:11 +0200 Subject: [PATCH 022/103] changed documentation --- docs/src/masterfileattributes.rst | 4 ++-- slsDetectorSoftware/generator/commands.yaml | 2 +- slsDetectorSoftware/include/sls/Detector.h | 3 ++- slsDetectorSoftware/src/Caller.cpp | 2 +- slsReceiverSoftware/src/DataProcessor.h | 8 ++++---- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/src/masterfileattributes.rst b/docs/src/masterfileattributes.rst index 777c930ff..5190ee647 100644 --- a/docs/src/masterfileattributes.rst +++ b/docs/src/masterfileattributes.rst @@ -345,8 +345,8 @@ Chip Test Board +-----------------------+-------------------------------------------------+ | Dbit Offset | Digital offset of valid data in bytes | +-----------------------+-------------------------------------------------+ - | Dbit Reorder | Group bits of all samples together to have them | - | | contiguous in the file | + | Dbit Reorder | Reorder such that it groups each signal (0-63) | + | | from all the different samples together | +-----------------------+-------------------------------------------------+ | Dbit Bitset | Digital 64 bit mask of bits enabled in receiver | +-----------------------+-------------------------------------------------+ diff --git a/slsDetectorSoftware/generator/commands.yaml b/slsDetectorSoftware/generator/commands.yaml index 47e2e1898..f33cf7be2 100644 --- a/slsDetectorSoftware/generator/commands.yaml +++ b/slsDetectorSoftware/generator/commands.yaml @@ -1414,7 +1414,7 @@ lock: rx_dbitreorder: - help: "[0, 1]\n\t[Ctb] Reorder digital data to group together all samples per signal. Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together." + help: "[0, 1]\n\t[Ctb] Reorder digital data such that it groups each signal (0-63) from all the different samples together . Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together." inherit_actions: INTEGER_COMMAND_VEC_ID actions: GET: diff --git a/slsDetectorSoftware/include/sls/Detector.h b/slsDetectorSoftware/include/sls/Detector.h index 8096f75a7..eb679a578 100644 --- a/slsDetectorSoftware/include/sls/Detector.h +++ b/slsDetectorSoftware/include/sls/Detector.h @@ -1732,7 +1732,8 @@ class Detector { /** [CTB] */ Result getRxDbitReorder(Positions pos = {}) const; - /** [CTB] Reorder digital data to group together all samples per signal. + /** [CTB] Reorder digital data such that it groups each signal (0-63) + * from all the different samples together. * Default is true. Setting to false means 'do not reorder' and to keep what * the board spits out, which is that all signals in a sample are grouped * together */ diff --git a/slsDetectorSoftware/src/Caller.cpp b/slsDetectorSoftware/src/Caller.cpp index c284b1957..78a6f79d7 100644 --- a/slsDetectorSoftware/src/Caller.cpp +++ b/slsDetectorSoftware/src/Caller.cpp @@ -10706,7 +10706,7 @@ std::string Caller::rx_dbitreorder(int action) { // print help if (action == slsDetectorDefs::HELP_ACTION) { os << R"V0G0N([0, 1] - [Ctb] Reorder digital data to group together all samples per signal. Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together. )V0G0N" + [Ctb] Reorder digital data such that it groups each signal (0-63) from all the different samples together . Default is 1. Setting to 0 means 'do not reorder' and to keep what the board spits out, which is that all signals in a sample are grouped together. )V0G0N" << std::endl; return os.str(); } diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index fd5b9dc1f..6e70072cd 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -96,14 +96,14 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { /** * Align corresponding digital bits together (CTB only if ctbDbitlist is not * empty) - * set variable reorder to true if, data should be rearranged such that - * individual digital bits from all samples are consecutive in memory + * set variable reorder to true if data should be rearranged such that + * it groups each signal (0-63) from all the different samples together */ void ArrangeDbitData(size_t &size, char *data); /** - * reorder datastream such that individual digital bits from all samples are - * stored consecutively in memory + * reorder datastream such that each signal (0-63) from all the different + * samples are grouped together and stored consecutively in memory */ void Reorder(size_t &size, char *data); From 6740d9b36354b799dd3a8d29801628960b9043ab Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Wed, 9 Apr 2025 09:20:05 +0200 Subject: [PATCH 023/103] alignedData now uses std::align_alloc --- pyctbgui/pyctbgui/services/Signals.py | 46 +++++++++-------- slsReceiverSoftware/CMakeLists.txt | 1 + slsReceiverSoftware/include/sls/utils.h | 36 +++++++++++++ slsReceiverSoftware/src/DataProcessor.cpp | 62 ++++------------------- 4 files changed, 70 insertions(+), 75 deletions(-) create mode 100644 slsReceiverSoftware/include/sls/utils.h diff --git a/pyctbgui/pyctbgui/services/Signals.py b/pyctbgui/pyctbgui/services/Signals.py index 4f27ab886..856571e22 100644 --- a/pyctbgui/pyctbgui/services/Signals.py +++ b/pyctbgui/pyctbgui/services/Signals.py @@ -90,7 +90,7 @@ class SignalsTab(QtWidgets.QWidget): self.legend.addItem(plot, name) @recordOrApplyPedestal - def _processWaveformData(self, data, aSamples, dSamples, rx_reorder, rx_dbitlist, isPlottedArray, romode, + def _processWaveformData(self, data, aSamples, dSamples, rx_dbitreorder, rx_dbitlist, isPlottedArray, romode, nADCEnabled): #transform raw waveform data into a processed numpy array @@ -100,44 +100,46 @@ class SignalsTab(QtWidgets.QWidget): if romode == 2: start_digital_data += nADCEnabled * 2 * aSamples digital_array = np.array(np.frombuffer(data, offset=start_digital_data, dtype=np.uint8)) - if rx_reorder: - arr = np.empty((len(rx_dbitlist), dSamples), dtype=np.uint8) + if rx_dbitreorder: + samples_per_bit = np.empty((len(rx_dbitlist), dSamples), dtype=np.uint8) #stored per row all the corresponding signals of all samples nbitsPerDBit = dSamples if nbitsPerDBit % 8 != 0: nbitsPerDBit += (8 - (dSamples % 8)) - offset = 0 + bit_index = 0 for idx, i in enumerate(rx_dbitlist): # where numbits * numsamples is not a multiple of 8 - if offset % 8 != 0: - offset += (8 - (offset % 8)) + if bit_index % 8 != 0: + bit_index += (8 - (bit_index % 8)) if not isPlottedArray[i]: - offset += nbitsPerDBit - arr[idx, :] = np.nan + bit_index += nbitsPerDBit + samples_per_bit[idx, :] = np.nan continue for iSample in range(dSamples): # all samples for digital bit together from slsReceiver - index = int(offset / 8) - iBit = offset % 8 + index = int(bit_index / 8) + iBit = bit_index % 8 bit = (digital_array[index] >> iBit) & 1 - arr[idx,iSample] = bit - offset += 1 - return arr + samples_per_bit[idx,iSample] = bit + bit_index += 1 + return samples_per_bit else: nbitsPerSample = len(rx_dbitlist) if len(rx_dbitlist) % 8 == 0 else len(rx_dbitlist) + (8 - (len(rx_dbitlist) % 8)) - arr = np.empty((dSamples, len(rx_dbitlist)), dtype=np.uint8) #store all samples + bits_per_sample = np.empty((dSamples, len(rx_dbitlist)), dtype=np.uint8) #store per row all selected bits of a sample for iSample in range(dSamples): - offset = nbitsPerSample * iSample + bit_index = nbitsPerSample * iSample for idx, i in enumerate(rx_dbitlist): if not isPlottedArray[i]: - offset += 1 - arr[iSample, idx] = np.nan + bit_index += 1 + bits_per_sample[iSample, idx] = np.nan - index = int(offset/8) + index = int(bit_index/8) iBit = idx % 8 bit = (digital_array[index] >> iBit) & 1 - arr[iSample, idx] = bit - offset += 1 - return arr.T.copy() + bits_per_sample[iSample, idx] = bit + bit_index += 1 + return bits_per_sample.T.copy() + + def processWaveformData(self, data, aSamples, dSamples): @@ -157,7 +159,7 @@ class SignalsTab(QtWidgets.QWidget): self.mainWindow.nADCEnabled) irow = 0 - for idx, i in enumerate(self.rx_dbitlist): #TODO i think ctBitlist is reordered CHECK!!! + for idx, i in enumerate(self.rx_dbitlist): # bits enabled but not plotting waveform = digital_array[idx, :] if np.isnan(waveform[0]): diff --git a/slsReceiverSoftware/CMakeLists.txt b/slsReceiverSoftware/CMakeLists.txt index 543ff7467..9e6ad75c3 100755 --- a/slsReceiverSoftware/CMakeLists.txt +++ b/slsReceiverSoftware/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES set(PUBLICHEADERS include/sls/Receiver.h + include/sls/utils.h ) # HDF5 file writing diff --git a/slsReceiverSoftware/include/sls/utils.h b/slsReceiverSoftware/include/sls/utils.h new file mode 100644 index 000000000..3aa76456c --- /dev/null +++ b/slsReceiverSoftware/include/sls/utils.h @@ -0,0 +1,36 @@ +/** + * @file utils.cpp + * @short utility objects for Receiver + */ + +#include +#include + +namespace sls { + +/* + * AlignedData + * Aligns data to a given type T with proper alignment + * @param data: pointer to data + * @param size: size of data to align in bytes + */ +template struct AlignedData { + T *aligned_ptr; // aligned data pointer + + AlignedData(char *data, size_t size) { + if (reinterpret_cast(data) % alignof(uint64_t) == 0) { + // If aligned directly cast to pointer + aligned_ptr = reinterpret_cast(data); + + } else { + + auto alignedbuffer = std::aligned_alloc(alignof(T), size); + std::memcpy(alignedbuffer, data, size); + aligned_ptr = reinterpret_cast(alignedbuffer); + } + } + + ~AlignedData() { std::free(aligned_ptr); } +}; + +} // namespace sls \ No newline at end of file diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index b96f5042a..6a16284dd 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -24,51 +24,10 @@ #include #include +#include "sls/utils.h" + namespace sls { -// TODO: move somewhere else -template struct AlignedData { - T *ptr; // Aligned data pointer - std::unique_ptr[]> buffer; - - AlignedData( - T *p, - std::unique_ptr[]> buf) - : ptr(p), buffer(std::move(buf)) {} -}; - -// TODO: should not be in this file any suggestions to move it to a more -// appropriate file? -// TODO: Add unit test -/* - * AlignData - * Aligns data to a given type T with proper alignment - * @param data: pointer to data - * @param size: size of data to align in bytes - * @return: aligned data - */ -template -void AlignData(AlignedData &aligneddata, char *data, size_t size) { - using AlignedBuffer = - typename std::aligned_storage::type; - std::unique_ptr tempbuffer; - - if (reinterpret_cast(data) % alignof(uint64_t) == 0) { - // If aligned directly cast to pointer - - aligneddata.ptr = reinterpret_cast(data); - - } else { - // Allocate a temporary buffer with proper alignment - tempbuffer = std::make_unique(size / sizeof(T)); - // size = ctbDigitaldbt; - - std::memcpy(tempbuffer.get(), data, size); - aligneddata.buffer = std::move(tempbuffer); - aligneddata.ptr = reinterpret_cast(aligneddata.buffer.get()); - } -} - const std::string DataProcessor::typeName = "DataProcessor"; DataProcessor::DataProcessor(int index) : ThreadObject(index, typeName) { @@ -617,11 +576,10 @@ void DataProcessor::Reorder(size_t &size, char *data) { } // make sure data is aligned to 8 bytes before casting to uint64_t - AlignedData aligned_data(nullptr, nullptr); - AlignData(aligned_data, data + nAnalogDataBytes + ctbDbitOffset, - ctbDigitalDataBytes); + AlignedData aligned_data(data + nAnalogDataBytes + ctbDbitOffset, + ctbDigitalDataBytes); - uint64_t *source = aligned_data.ptr; + uint64_t *source = aligned_data.aligned_ptr; const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -694,13 +652,11 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { return; } - AlignedData aligned_data(nullptr, nullptr); - AlignData(aligned_data, data + nAnalogDataBytes + ctbDbitOffset, - ctbDigitalDataBytes); + // make sure data is aligned to 8 bytes before casting to uint64_t + AlignedData aligned_data(data + nAnalogDataBytes + ctbDbitOffset, + ctbDigitalDataBytes); - uint64_t *source = aligned_data.ptr; - - // auto *source = (uint64_t *)(data + nAnalogDataBytes + ctbDbitOffset); + uint64_t *source = aligned_data.aligned_ptr; const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); From 29fe988583d23fce8acf630c3bcd4ee366770735 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Wed, 9 Apr 2025 09:31:38 +0200 Subject: [PATCH 024/103] imagedata is now allocated on the heap --- .../ctbDetectorServer/slsDetectorFunctionList.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index f1f33cb84..8e32f3b63 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -15,6 +15,7 @@ #include "loadPattern.h" #include +#include #include #include // usleep #ifdef VIRTUAL @@ -2258,8 +2259,9 @@ void *start_timer(void *arg) { int packetsPerFrame = ceil((double)imageSize / (double)dataSize); // Generate Data - char imageData[imageSize]; // + char *imageData = (char *)malloc(imageSize); memset(imageData, 0, imageSize); + /* for (int i = 0; i < imageSize; i += sizeof(uint16_t)) { *((uint16_t *)(imageData + i)) = i; @@ -2325,6 +2327,8 @@ void *start_timer(void *arg) { setNextFrameNumber(frameNr + numFrames); } + free(imageData); + closeUDPSocket(0); sharedMemory_setStatus(IDLE); From a138b5b3650dd1cb86c2b15bdb4cbe9378b32e9e Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 9 Apr 2025 17:52:23 +0200 Subject: [PATCH 025/103] m3 server fix for trimbits and badchannels that are shifted by 1 --- slsDetectorServers/mythen3DetectorServer/mythen3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsDetectorServers/mythen3DetectorServer/mythen3.c b/slsDetectorServers/mythen3DetectorServer/mythen3.c index f4dc01871..a03fd736d 100644 --- a/slsDetectorServers/mythen3DetectorServer/mythen3.c +++ b/slsDetectorServers/mythen3DetectorServer/mythen3.c @@ -304,7 +304,7 @@ patternParameters *setChannelRegisterChip(int ichip, char *mask, chanReg |= (0x1 << (3 + icounter)); } } - + chanReg /= 2; // deserialize if (chanReg & CHAN_REG_BAD_CHANNEL_MSK) { LOG(logINFOBLUE, From f8b12201f861327c6ff651c57ba2dcafeddfde29 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 9 Apr 2025 18:21:54 +0200 Subject: [PATCH 026/103] binary in --- .../bin/mythen3DetectorServer_developer | Bin 305788 -> 305788 bytes slsSupportLib/include/sls/versionAPI.h | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/slsDetectorServers/mythen3DetectorServer/bin/mythen3DetectorServer_developer b/slsDetectorServers/mythen3DetectorServer/bin/mythen3DetectorServer_developer index 0ac0007c5d0c7bf4e7e220c11c1e77fbf4094157..bc4c3055261aeeb31032107294784e2efff275e8 100755 GIT binary patch delta 19030 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 diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index f37cd7b42..b9a1d824d 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -5,8 +5,8 @@ #define APIRECEIVER "developer 0x241122" #define APICTB "developer 0x250310" #define APIGOTTHARD2 "developer 0x250310" -#define APIMYTHEN3 "developer 0x250310" #define APIMOENCH "developer 0x250310" #define APIEIGER "developer 0x250310" #define APIXILINXCTB "developer 0x250311" #define APIJUNGFRAU "developer 0x250318" +#define APIMYTHEN3 "developer 0x250409" From f119d14e7c0dc1e809e7894744e0ee72d7e17a5b Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Thu, 10 Apr 2025 11:29:01 +0200 Subject: [PATCH 027/103] added check for proper memory allocation --- .../ctbDetectorServer/slsDetectorFunctionList.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index 8e32f3b63..83d7a111c 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -2262,6 +2262,11 @@ void *start_timer(void *arg) { char *imageData = (char *)malloc(imageSize); memset(imageData, 0, imageSize); + if (imageData == NULL) { + LOG(logERROR, ("Can not allocate image Data RAM." + "Probable cause: Memory Leak.\n")); + return FAIL; + } /* for (int i = 0; i < imageSize; i += sizeof(uint16_t)) { *((uint16_t *)(imageData + i)) = i; From 361437428dc7110cfa26f301ae0f7f452b55483e Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 10 Apr 2025 13:11:16 +0200 Subject: [PATCH 028/103] commenting out the example in receiver data call back changing size as it affects users using debugging mode to print out headers --- slsReceiverSoftware/src/MultiReceiverApp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slsReceiverSoftware/src/MultiReceiverApp.cpp b/slsReceiverSoftware/src/MultiReceiverApp.cpp index 061c9a441..e66e8a90c 100644 --- a/slsReceiverSoftware/src/MultiReceiverApp.cpp +++ b/slsReceiverSoftware/src/MultiReceiverApp.cpp @@ -146,8 +146,9 @@ void GetData(slsDetectorDefs::sls_receiver_header &header, // header->packetsMask.to_string().c_str(), ((uint8_t)(*((uint8_t *)(dataPointer)))), imageSize); - // if data is modified, eg ROI and size is reduced - imageSize = 26000; + // if data is modified, can affect size + // only reduction in size allowed, not increase + // imageSize = 26000; } /** From 721d5363501832b2ec82158d50d90d1bba9e1cd5 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Thu, 10 Apr 2025 13:31:47 +0200 Subject: [PATCH 029/103] fixed warnings --- slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index 83d7a111c..d7f9e4f56 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -2265,7 +2265,7 @@ void *start_timer(void *arg) { if (imageData == NULL) { LOG(logERROR, ("Can not allocate image Data RAM." "Probable cause: Memory Leak.\n")); - return FAIL; + return NULL; } /* for (int i = 0; i < imageSize; i += sizeof(uint16_t)) { From 7c652498e4a93fbc9b4d8cfb5d349718ad314710 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Thu, 10 Apr 2025 17:34:39 +0200 Subject: [PATCH 030/103] got rid of cast to uint64 --- slsReceiverSoftware/CMakeLists.txt | 1 - slsReceiverSoftware/include/sls/utils.h | 36 ----------------------- slsReceiverSoftware/src/DataProcessor.cpp | 29 +++++++++--------- 3 files changed, 15 insertions(+), 51 deletions(-) delete mode 100644 slsReceiverSoftware/include/sls/utils.h diff --git a/slsReceiverSoftware/CMakeLists.txt b/slsReceiverSoftware/CMakeLists.txt index 9e6ad75c3..543ff7467 100755 --- a/slsReceiverSoftware/CMakeLists.txt +++ b/slsReceiverSoftware/CMakeLists.txt @@ -17,7 +17,6 @@ set(SOURCES set(PUBLICHEADERS include/sls/Receiver.h - include/sls/utils.h ) # HDF5 file writing diff --git a/slsReceiverSoftware/include/sls/utils.h b/slsReceiverSoftware/include/sls/utils.h deleted file mode 100644 index 3aa76456c..000000000 --- a/slsReceiverSoftware/include/sls/utils.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @file utils.cpp - * @short utility objects for Receiver - */ - -#include -#include - -namespace sls { - -/* - * AlignedData - * Aligns data to a given type T with proper alignment - * @param data: pointer to data - * @param size: size of data to align in bytes - */ -template struct AlignedData { - T *aligned_ptr; // aligned data pointer - - AlignedData(char *data, size_t size) { - if (reinterpret_cast(data) % alignof(uint64_t) == 0) { - // If aligned directly cast to pointer - aligned_ptr = reinterpret_cast(data); - - } else { - - auto alignedbuffer = std::aligned_alloc(alignof(T), size); - std::memcpy(alignedbuffer, data, size); - aligned_ptr = reinterpret_cast(alignedbuffer); - } - } - - ~AlignedData() { std::free(aligned_ptr); } -}; - -} // namespace sls \ No newline at end of file diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 6a16284dd..6870442a1 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -24,8 +24,6 @@ #include #include -#include "sls/utils.h" - namespace sls { const std::string DataProcessor::typeName = "DataProcessor"; @@ -576,10 +574,11 @@ void DataProcessor::Reorder(size_t &size, char *data) { } // make sure data is aligned to 8 bytes before casting to uint64_t - AlignedData aligned_data(data + nAnalogDataBytes + ctbDbitOffset, - ctbDigitalDataBytes); + // AlignedData aligned_data(data + nAnalogDataBytes + + // ctbDbitOffset, ctbDigitalDataBytes); - uint64_t *source = aligned_data.aligned_ptr; + char *source = + data + nAnalogDataBytes + ctbDbitOffset; // aligned_data.aligned_ptr; const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -652,11 +651,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { return; } - // make sure data is aligned to 8 bytes before casting to uint64_t - AlignedData aligned_data(data + nAnalogDataBytes + ctbDbitOffset, - ctbDigitalDataBytes); - - uint64_t *source = aligned_data.aligned_ptr; + char *source = (data + nAnalogDataBytes + ctbDbitOffset); const int numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); @@ -694,10 +689,13 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { ++dest; } + uint8_t byte_index = bi / 8; + // loop through the frame digital data - for (auto *ptr = source; ptr < (source + numDigitalSamples);) { + for (auto *ptr = source + byte_index; + ptr < (source + 8 * numDigitalSamples); ptr += 8) { // get selected bit from each 8 bit - uint8_t bit = (*ptr++ >> bi) & 1; + uint8_t bit = (*ptr >> bi % 8) & 1; *dest |= bit << bitoffset; // stored as least significant ++bitoffset; // extract destination in 8 bit batches @@ -710,7 +708,8 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { } else { // loop through the digital data int bitoffset = 0; - for (auto *ptr = source; ptr < (source + numDigitalSamples); ++ptr) { + for (auto *ptr = source; ptr < (source + 8 * numDigitalSamples); + ptr += 8) { // where bit enable vector size is not a multiple of 8 if (bitoffset != 0) { bitoffset = 0; @@ -720,7 +719,9 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { // loop through digital bit enable vector for (auto bi : ctbDbitList) { // get selected bit from each 64 bit - uint8_t bit = (*ptr >> bi) & 1; + uint8_t byte_index = bi / 8; + + uint8_t bit = (*(ptr + byte_index) >> (bi % 8)) & 1; *dest |= bit << bitoffset; ++bitoffset; // extract destination in 8 bit batches From 5be0724f821d6cc8473496aacf2c69116edeaf53 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Thu, 10 Apr 2025 17:52:16 +0200 Subject: [PATCH 031/103] got rid of Reorder function --- slsReceiverSoftware/src/DataProcessor.cpp | 82 +------------------ slsReceiverSoftware/src/DataProcessor.h | 8 +- .../tests/test-ArrangeDataBasedOnBitList.cpp | 18 ++-- 3 files changed, 15 insertions(+), 93 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 6870442a1..b70199672 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace sls { @@ -362,7 +363,9 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, if (!ctbDbitList.empty()) { ArrangeDbitData(size, data); } else if (ctbDbitReorder) { - Reorder(size, data); + ctbDbitList.resize(64); + std::iota(ctbDbitList.begin(), ctbDbitList.end(), 0); + ArrangeDbitData(size, data); } else if (ctbDbitOffset > 0) { RemoveTrailingBits(size, data); } @@ -558,83 +561,6 @@ void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { size = nAnalogDataBytes + ctbDigitalDataBytes + nTransceiverDataBytes; } -void DataProcessor::Reorder(size_t &size, char *data) { - const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); - const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); - const size_t nTransceiverDataBytes = - generalData->GetNumberOfTransceiverDatabytes(); - - const size_t ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; - - // no digital data - if (ctbDigitalDataBytes == 0) { - LOG(logWARNING) - << "No digital data for call back, yet reorder is set to 1."; - return; - } - - // make sure data is aligned to 8 bytes before casting to uint64_t - // AlignedData aligned_data(data + nAnalogDataBytes + - // ctbDbitOffset, ctbDigitalDataBytes); - - char *source = - data + nAnalogDataBytes + ctbDbitOffset; // aligned_data.aligned_ptr; - - const size_t numDigitalSamples = (ctbDigitalDataBytes / sizeof(uint64_t)); - - size_t numBytesPerBit = - (numDigitalSamples % 8 == 0) - ? numDigitalSamples / 8 - : numDigitalSamples / 8 + - 1; // number of bytes per bit in digital data after reordering - - size_t totalNumBytes = - numBytesPerBit * - 64; // number of bytes for digital data after reordering - - std::vector result(totalNumBytes, 0); - uint8_t *dest = &result[0]; - - int bitoffset = 0; - // reorder - for (size_t bi = 0; bi < 64; ++bi) { - - if (bitoffset != 0) { - bitoffset = 0; - ++dest; - } - - for (auto *ptr = source; ptr < (source + numDigitalSamples); ++ptr) { - uint8_t bit = (*ptr >> bi) & 1; - *dest |= bit << bitoffset; // most significant bits will be padded - ++bitoffset; - if (bitoffset == 8) { - bitoffset = 0; - ++dest; - } - } - } - - // move transceiver data to not overwrite and avoid gap in memory - if (totalNumBytes != nDigitalDataBytes) - memmove(data + nAnalogDataBytes + totalNumBytes * sizeof(uint8_t), - data + nAnalogDataBytes + nDigitalDataBytes, - nTransceiverDataBytes); - - // copy back to memory and update size - size = totalNumBytes * sizeof(uint8_t) + nAnalogDataBytes + - nTransceiverDataBytes; - - memcpy(data + nAnalogDataBytes, result.data(), - totalNumBytes * sizeof(uint8_t)); - - LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes - << " nAnalogDataBytes:" << nAnalogDataBytes - << " ctbDbitOffset:" << ctbDbitOffset - << " nTransceiverDataBytes:" << nTransceiverDataBytes - << " size:" << size; -} - /** ctb specific */ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 6e70072cd..6d66646e8 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -101,12 +101,6 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { */ void ArrangeDbitData(size_t &size, char *data); - /** - * reorder datastream such that each signal (0-63) from all the different - * samples are grouped together and stored consecutively in memory - */ - void Reorder(size_t &size, char *data); - /** * remove trailing bits in digital data stream */ @@ -179,7 +173,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t currentFreqCount{0}; struct timespec timerbegin{}; bool framePadding; - std::vector ctbDbitList; + std::vector ctbDbitList{}; int ctbDbitOffset{0}; bool ctbDbitReorder{true}; std::atomic startedFlag{false}; diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index af75c3b54..a2909db25 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -39,16 +39,12 @@ class GeneralDataTest : public GeneralData { // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0){}; - ~DataProcessorTest(){}; + DataProcessorTest() : DataProcessor(0) {}; + ~DataProcessorTest() {}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } - void Reorder(size_t &size, char *data) { - DataProcessor::Reorder(size, data); - } - void RemoveTrailingBits(size_t &size, char *data) { DataProcessor::RemoveTrailingBits(size, data); } @@ -183,6 +179,9 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Reorder all", set_num_samples(num_samples); set_data(); + std::vector bitlist(64); + std::iota(bitlist.begin(), bitlist.end(), 0); + dataprocessor->SetCtbDbitList(bitlist); dataprocessor->SetCtbDbitReorder(true); // set reorder to true const size_t expected_size = @@ -202,7 +201,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Reorder all", num_transceiver_bytes); // set to 125 size_t size = get_size(); - dataprocessor->Reorder(size, data); // call reorder + dataprocessor->ArrangeDbitData(size, data); // call reorder CHECK(size == expected_size); CHECK(memcmp(data, expected_data, expected_size) == 0); @@ -219,6 +218,9 @@ TEST_CASE_METHOD(DataProcessorTestFixture, set_random_offset_bytes(num_random_offset_bytes); set_data(); + std::vector bitlist(64); + std::iota(bitlist.begin(), bitlist.end(), 0); + dataprocessor->SetCtbDbitList(bitlist); dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); dataprocessor->SetCtbDbitReorder(true); // set reorder to true @@ -242,7 +244,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, num_transceiver_bytes); // set to 125 size_t size = get_size(); - dataprocessor->Reorder(size, data); // call reorder + dataprocessor->ArrangeDbitData(size, data); // call reorder CHECK(size == expected_size); CHECK(memcmp(data, expected_data, expected_size) == 0); From 4c86ad3198142db144de8ea70e408a2587754c6a Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 10:27:26 +0200 Subject: [PATCH 032/103] added sanity check to only enable for chipttestboard and xilinx --- slsReceiverSoftware/src/DataProcessor.cpp | 15 +++++++++++++++ .../tests/test-ArrangeDataBasedOnBitList.cpp | 2 ++ 2 files changed, 17 insertions(+) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index b70199672..7a69e3f41 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -336,6 +336,7 @@ void DataProcessor::StopProcessing(char *buf) { void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, size_t &firstImageIndex, char *data) { + uint64_t fnum = header.detHeader.frameNumber; LOG(logDEBUG1) << "DataProcessing " << index << ": fnum:" << fnum; currentFrameIndex = fnum; @@ -540,6 +541,13 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { } void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { + + if (!(generalData->detType == slsDetectorDefs::CHIPTESTBOARD || + generalData->detType == slsDetectorDefs::XILINX_CHIPTESTBOARD)) { + throw std::runtime_error("behavior undefined for detector " + + std::to_string(generalData->detType)); + } + const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); const size_t nTransceiverDataBytes = @@ -563,6 +571,13 @@ void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { /** ctb specific */ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { + + if (!(generalData->detType == slsDetectorDefs::CHIPTESTBOARD || + generalData->detType == slsDetectorDefs::XILINX_CHIPTESTBOARD)) { + throw std::runtime_error("behavior undefined for detector " + + std::to_string(generalData->detType)); + } + size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); size_t nTransceiverDataBytes = diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index a2909db25..0b5ef3bd0 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -16,6 +16,8 @@ namespace sls { class GeneralDataTest : public GeneralData { public: + GeneralDataTest() { detType = slsDetectorDefs::CHIPTESTBOARD; } + int GetNumberOfAnalogDatabytes() { return nAnalogBytes; }; int GetNumberOfDigitalDatabytes() { return nDigitalBytes; }; From f9bc2eb1269872e8c4cd380d1290304be65e92d6 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 10:32:31 +0200 Subject: [PATCH 033/103] removed Gotthard stuff --- slsReceiverSoftware/src/DataProcessor.cpp | 10 ---------- slsReceiverSoftware/src/GeneralData.h | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 7a69e3f41..1956d3578 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -515,16 +515,6 @@ void DataProcessor::PadMissingPackets(sls_receiver_header header, char *data) { // missing packet switch (generalData->detType) { - // for gotthard, 1st packet: 4 bytes fnum, CACA + CACA, 639*2 bytes - // data - // 2nd packet: 4 bytes fnum, previous 1*2 bytes data + - // 640*2 bytes data !! - case GOTTHARD: - if (pnum == 0u) - memset(data + (pnum * dsize), 0xFF, dsize - 2); - else - memset(data + (pnum * dsize), 0xFF, dsize + 2); - break; case CHIPTESTBOARD: case XILINX_CHIPTESTBOARD: if (pnum == (pperFrame - 1)) diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 9f3b43429..6d9a5d626 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -60,8 +60,8 @@ class GeneralData { slsDetectorDefs::frameDiscardPolicy frameDiscardMode{ slsDetectorDefs::NO_DISCARD}; - GeneralData(){}; - virtual ~GeneralData(){}; + GeneralData() {}; + virtual ~GeneralData() {}; // Returns the pixel depth in byte, 4 bits being 0.5 byte float GetPixelDepth() { return float(dynamicRange) / 8; } From 01cc745787e7336693d62430f650d395ebd47ba0 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 11 Apr 2025 10:30:28 +0200 Subject: [PATCH 034/103] update the comment about how to modify data on a data call back from the receiver --- slsReceiverSoftware/src/MultiReceiverApp.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/slsReceiverSoftware/src/MultiReceiverApp.cpp b/slsReceiverSoftware/src/MultiReceiverApp.cpp index e66e8a90c..44284281c 100644 --- a/slsReceiverSoftware/src/MultiReceiverApp.cpp +++ b/slsReceiverSoftware/src/MultiReceiverApp.cpp @@ -146,9 +146,21 @@ void GetData(slsDetectorDefs::sls_receiver_header &header, // header->packetsMask.to_string().c_str(), ((uint8_t)(*((uint8_t *)(dataPointer)))), imageSize); - // if data is modified, can affect size - // only reduction in size allowed, not increase - // imageSize = 26000; + // // example of how to use roi or modify data that is later written to file + // slsDetectorDefs::ROI roi{0, 10, 0, 20}; + // int width = roi.xmax - roi.xmin; + // int height = roi.ymax - roi.ymin; + // uint8_t *destPtr = (uint8_t *)dataPointer; + // for (int irow = roi.ymin; irow < roi.ymax; ++irow) { + // memcpy(destPtr, + // ((uint8_t *)(dataPointer + irow * callbackHeader.shape.x + + // roi.xmin)), + // width); + // destPtr += width; + // } + // memcpy((uint8_t*)dataPointer, (uint8_t*)dataPointer + // // setting roi for eg. changes size + // imageSize = width * height; } /** From 9d8f9a9ba92a7e407a71b437f2a58e6d1708664a Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 10:45:02 +0200 Subject: [PATCH 035/103] autogenerated commands and make format --- slsDetectorGui/include/qDefs.h | 2 +- .../slsDetectorServer_defs.h | 8 +++----- .../eigerDetectorServer/FebControl.c | 6 +++--- .../slsDetectorServer_defs.h | 3 +-- .../slsDetectorServer_defs.h | 3 +-- .../slsDetectorServer_defs.h | 20 ++++++++----------- .../slsDetectorServer_defs.h | 8 +++----- .../slsDetectorServer_defs.h | 18 +++++++---------- .../generator/extended_commands.yaml | 7 ++++--- slsDetectorSoftware/include/sls/Result.h | 2 +- .../include/sls/detectorData.h | 6 +++--- slsReceiverSoftware/src/DataProcessor.h | 2 +- slsReceiverSoftware/src/File.h | 4 ++-- slsSupportLib/include/sls/ClientSocket.h | 12 +++++------ slsSupportLib/include/sls/Timer.h | 2 +- slsSupportLib/include/sls/logger.h | 2 +- slsSupportLib/include/sls/sls_detector_defs.h | 6 +++--- slsSupportLib/src/ZmqSocket.cpp | 2 +- 18 files changed, 50 insertions(+), 63 deletions(-) diff --git a/slsDetectorGui/include/qDefs.h b/slsDetectorGui/include/qDefs.h index 375d62d6c..018790c46 100644 --- a/slsDetectorGui/include/qDefs.h +++ b/slsDetectorGui/include/qDefs.h @@ -39,7 +39,7 @@ class qDefs : public QWidget { /** * Empty Constructor */ - qDefs(){}; + qDefs() {}; static QFont GetDefaultFont() { return QFont("Cantarell", 10, QFont::Normal); diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h index 683338861..103502f1f 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h @@ -7,11 +7,9 @@ #define MIN_REQRD_VRSN_T_RD_API 0x181130 #define REQRD_FRMWR_VRSN 0x230705 -#define NUM_HARDWARE_VERSIONS (1) -#define HARDWARE_VERSION_NUMBERS \ - { 0x3f } -#define HARDWARE_VERSION_NAMES \ - { "5.1" } +#define NUM_HARDWARE_VERSIONS (1) +#define HARDWARE_VERSION_NUMBERS {0x3f} +#define HARDWARE_VERSION_NAMES {"5.1"} #define LINKED_SERVER_NAME "ctbDetectorServer" diff --git a/slsDetectorServers/eigerDetectorServer/FebControl.c b/slsDetectorServers/eigerDetectorServer/FebControl.c index f0b96d487..d3750259a 100644 --- a/slsDetectorServers/eigerDetectorServer/FebControl.c +++ b/slsDetectorServers/eigerDetectorServer/FebControl.c @@ -634,8 +634,8 @@ int Feb_Control_SetTrimbits(unsigned int *trimbits, int top) { << ((7 - i) * 4); // upper } } // end column loop i - } // end supercolumn loop sc - } // end row loop + } // end supercolumn loop sc + } // end row loop if (Feb_Control_activated) { if (!Feb_Interface_WriteMemoryInLoops(Feb_Control_leftAddress, @@ -652,7 +652,7 @@ int Feb_Control_SetTrimbits(unsigned int *trimbits, int top) { } } // end row_set loop (groups of 16 rows) - } // end l_r loop + } // end l_r loop memcpy(Feb_Control_last_downloaded_trimbits, trimbits, Feb_Control_trimbit_size * sizeof(unsigned int)); diff --git a/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h index 5e4a62f1b..aa87505b3 100644 --- a/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h @@ -7,8 +7,7 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x1}; -#define HARDWARE_VERSION_NAMES \ - { "FX70T", "FX30T" } +#define HARDWARE_VERSION_NAMES {"FX70T", "FX30T"} #define REQUIRED_FIRMWARE_VERSION (32) // virtual ones renamed for consistency diff --git a/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h index d275cb400..a696221c9 100644 --- a/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h @@ -9,8 +9,7 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x2}; -#define HARDWARE_VERSION_NAMES \ - { "1.0", "1.2" } +#define HARDWARE_VERSION_NAMES {"1.0", "1.2"} #define LINKED_SERVER_NAME "gotthard2DetectorServer" diff --git a/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h index cc2b0928b..88e30f1c4 100644 --- a/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h @@ -8,11 +8,9 @@ #define REQRD_FRMWRE_VRSN_BOARD2 0x250209 // 1.0 pcb (version = 010) #define REQRD_FRMWRE_VRSN 0x250208 // 2.0 pcb (version = 011) -#define NUM_HARDWARE_VERSIONS (2) -#define HARDWARE_VERSION_NUMBERS \ - { 0x2, 0x3 } -#define HARDWARE_VERSION_NAMES \ - { "1.0", "2.0" } +#define NUM_HARDWARE_VERSIONS (2) +#define HARDWARE_VERSION_NUMBERS {0x2, 0x3} +#define HARDWARE_VERSION_NAMES {"1.0", "2.0"} #define ID_FILE "detid_jungfrau.txt" #define LINKED_SERVER_NAME "jungfrauDetectorServer" @@ -214,13 +212,11 @@ enum DACINDEX { enum MASTERINDEX { MASTER_HARDWARE, OW_MASTER, OW_SLAVE }; #define MASTER_NAMES "hardware", "master", "slave" -#define NUMSETTINGS (2) -#define NSPECIALDACS (3) -#define SPECIALDACINDEX {J_VREF_PRECH, J_VREF_DS, J_VREF_COMP}; -#define SPECIAL_DEFAULT_DYNAMIC_GAIN_VALS \ - { 1450, 480, 420 } -#define SPECIAL_DEFAULT_DYNAMICHG0_GAIN_VALS \ - { 1550, 450, 620 } +#define NUMSETTINGS (2) +#define NSPECIALDACS (3) +#define SPECIALDACINDEX {J_VREF_PRECH, J_VREF_DS, J_VREF_COMP}; +#define SPECIAL_DEFAULT_DYNAMIC_GAIN_VALS {1450, 480, 420} +#define SPECIAL_DEFAULT_DYNAMICHG0_GAIN_VALS {1550, 450, 620} enum NETWORKINDEX { TXN_FRAME, FLOWCTRL_10G }; enum CLKINDEX { RUN_CLK, ADC_CLK, DBIT_CLK, NUM_CLOCKS }; diff --git a/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h index aa4133d7a..22d8e68ba 100644 --- a/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h @@ -7,11 +7,9 @@ #define REQRD_FRMWRE_VRSN_BOARD2 0x444445 // 1.0 pcb (version = 010) #define REQRD_FRMWRE_VRSN 0x231026 // 2.0 pcb (version = 011) -#define NUM_HARDWARE_VERSIONS (2) -#define HARDWARE_VERSION_NUMBERS \ - { 0x2, 0x3 } -#define HARDWARE_VERSION_NAMES \ - { "1.0", "2.0" } +#define NUM_HARDWARE_VERSIONS (2) +#define HARDWARE_VERSION_NUMBERS {0x2, 0x3} +#define HARDWARE_VERSION_NAMES {"1.0", "2.0"} #define ID_FILE ("detid_moench.txt") #define LINKED_SERVER_NAME "moenchDetectorServer" diff --git a/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h index b0d8c1ea5..80abbd126 100644 --- a/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h @@ -9,8 +9,7 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x2}; -#define HARDWARE_VERSION_NAMES \ - { "1.0", "1.2" } +#define HARDWARE_VERSION_NAMES {"1.0", "1.2"} #define LINKED_SERVER_NAME "mythen3DetectorServer" @@ -130,15 +129,12 @@ enum DACINDEX { enum ADCINDEX { TEMP_FPGA }; -#define NUMSETTINGS (3) -#define NSPECIALDACS (2) -#define SPECIALDACINDEX {M_VRPREAMP, M_VRSHAPER}; -#define SPECIAL_DEFAULT_STANDARD_DAC_VALS \ - { 1100, 1280 } -#define SPECIAL_DEFAULT_FAST_DAC_VALS \ - { 300, 1500 } -#define SPECIAL_DEFAULT_HIGHGAIN_DAC_VALS \ - { 1300, 1100 } +#define NUMSETTINGS (3) +#define NSPECIALDACS (2) +#define SPECIALDACINDEX {M_VRPREAMP, M_VRSHAPER}; +#define SPECIAL_DEFAULT_STANDARD_DAC_VALS {1100, 1280} +#define SPECIAL_DEFAULT_FAST_DAC_VALS {300, 1500} +#define SPECIAL_DEFAULT_HIGHGAIN_DAC_VALS {1300, 1100} enum CLKINDEX { SYSTEM_C0, SYSTEM_C1, SYSTEM_C2, NUM_CLOCKS }; #define NUM_CLOCKS_TO_SET (1) diff --git a/slsDetectorSoftware/generator/extended_commands.yaml b/slsDetectorSoftware/generator/extended_commands.yaml index 4e4b52a72..6b9f72577 100644 --- a/slsDetectorSoftware/generator/extended_commands.yaml +++ b/slsDetectorSoftware/generator/extended_commands.yaml @@ -8341,9 +8341,10 @@ rx_dbitreorder: store_result_in_t: false command_name: rx_dbitreorder function_alias: rx_dbitreorder - help: "[0, 1]\n\t[Ctb] Reorder digital data to group together all samples per signal.\ - \ Default is 1. Setting to 0 means 'do not reorder' and to keep what the board\ - \ spits out, which is that all signals in a sample are grouped together." + help: "[0, 1]\n\t[Ctb] Reorder digital data such that it groups each signal (0-63)\ + \ from all the different samples together . Default is 1. Setting to 0 means 'do\ + \ not reorder' and to keep what the board spits out, which is that all signals\ + \ in a sample are grouped together." infer_action: true template: true rx_discardpolicy: diff --git a/slsDetectorSoftware/include/sls/Result.h b/slsDetectorSoftware/include/sls/Result.h index 0cb937408..479eae047 100644 --- a/slsDetectorSoftware/include/sls/Result.h +++ b/slsDetectorSoftware/include/sls/Result.h @@ -32,7 +32,7 @@ template > class Result { public: Result() = default; - Result(std::initializer_list list) : vec(list){}; + Result(std::initializer_list list) : vec(list) {}; /** Custom constructor from integer type to Result or Result */ template ctbDbitList{}; int ctbDbitOffset{0}; diff --git a/slsReceiverSoftware/src/File.h b/slsReceiverSoftware/src/File.h index c30a86238..70a3de224 100644 --- a/slsReceiverSoftware/src/File.h +++ b/slsReceiverSoftware/src/File.h @@ -16,8 +16,8 @@ namespace sls { class File : private virtual slsDetectorDefs { public: - File(){}; - virtual ~File(){}; + File() {}; + virtual ~File() {}; virtual fileFormat GetFileFormat() const = 0; virtual void CloseFile() = 0; diff --git a/slsSupportLib/include/sls/ClientSocket.h b/slsSupportLib/include/sls/ClientSocket.h index dcfc02b25..607f1f507 100644 --- a/slsSupportLib/include/sls/ClientSocket.h +++ b/slsSupportLib/include/sls/ClientSocket.h @@ -28,22 +28,22 @@ class ClientSocket : public DataSocket { class ReceiverSocket : public ClientSocket { public: ReceiverSocket(const std::string &hostname, uint16_t port_number) - : ClientSocket("Receiver", hostname, port_number){}; - ReceiverSocket(struct sockaddr_in addr) : ClientSocket("Receiver", addr){}; + : ClientSocket("Receiver", hostname, port_number) {}; + ReceiverSocket(struct sockaddr_in addr) : ClientSocket("Receiver", addr) {}; }; class DetectorSocket : public ClientSocket { public: DetectorSocket(const std::string &hostname, uint16_t port_number) - : ClientSocket("Detector", hostname, port_number){}; - DetectorSocket(struct sockaddr_in addr) : ClientSocket("Detector", addr){}; + : ClientSocket("Detector", hostname, port_number) {}; + DetectorSocket(struct sockaddr_in addr) : ClientSocket("Detector", addr) {}; }; class GuiSocket : public ClientSocket { public: GuiSocket(const std::string &hostname, uint16_t port_number) - : ClientSocket("Gui", hostname, port_number){}; - GuiSocket(struct sockaddr_in addr) : ClientSocket("Gui", addr){}; + : ClientSocket("Gui", hostname, port_number) {}; + GuiSocket(struct sockaddr_in addr) : ClientSocket("Gui", addr) {}; }; }; // namespace sls diff --git a/slsSupportLib/include/sls/Timer.h b/slsSupportLib/include/sls/Timer.h index b22909b33..7409bb8fe 100644 --- a/slsSupportLib/include/sls/Timer.h +++ b/slsSupportLib/include/sls/Timer.h @@ -33,5 +33,5 @@ class Timer { std::string name_; }; -}; // namespace sls +}; // namespace sls #endif // TIMER_H diff --git a/slsSupportLib/include/sls/logger.h b/slsSupportLib/include/sls/logger.h index 9b949d802..2f94c28dc 100644 --- a/slsSupportLib/include/sls/logger.h +++ b/slsSupportLib/include/sls/logger.h @@ -47,7 +47,7 @@ class Logger { public: Logger() = default; - explicit Logger(TLogLevel level) : level(level){}; + explicit Logger(TLogLevel level) : level(level) {}; ~Logger() { // output in the destructor to allow for << syntax os << RESET << '\n'; diff --git a/slsSupportLib/include/sls/sls_detector_defs.h b/slsSupportLib/include/sls/sls_detector_defs.h index 9a010ba4c..1fe0d9617 100644 --- a/slsSupportLib/include/sls/sls_detector_defs.h +++ b/slsSupportLib/include/sls/sls_detector_defs.h @@ -125,7 +125,7 @@ class slsDetectorDefs { int x{0}; int y{0}; xy() = default; - xy(int x, int y) : x(x), y(y){}; + xy(int x, int y) : x(x), y(y) {}; } __attribute__((packed)); #endif @@ -227,9 +227,9 @@ class slsDetectorDefs { int ymin{-1}; int ymax{-1}; ROI() = default; - ROI(int xmin, int xmax) : xmin(xmin), xmax(xmax){}; + ROI(int xmin, int xmax) : xmin(xmin), xmax(xmax) {}; ROI(int xmin, int xmax, int ymin, int ymax) - : xmin(xmin), xmax(xmax), ymin(ymin), ymax(ymax){}; + : xmin(xmin), xmax(xmax), ymin(ymin), ymax(ymax) {}; constexpr std::array getIntArray() const { return std::array({xmin, xmax, ymin, ymax}); } diff --git a/slsSupportLib/src/ZmqSocket.cpp b/slsSupportLib/src/ZmqSocket.cpp index 2fafe4799..eed0c7221 100644 --- a/slsSupportLib/src/ZmqSocket.cpp +++ b/slsSupportLib/src/ZmqSocket.cpp @@ -531,7 +531,7 @@ void ZmqSocket::PrintError() { // Nested class to do RAII handling of socket descriptors ZmqSocket::mySocketDescriptors::mySocketDescriptors(bool server) - : server(server), contextDescriptor(nullptr), socketDescriptor(nullptr){}; + : server(server), contextDescriptor(nullptr), socketDescriptor(nullptr) {}; ZmqSocket::mySocketDescriptors::~mySocketDescriptors() { Disconnect(); Close(); From 598154645c40e94b5cd1addd47ef311c39e38330 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 11:03:52 +0200 Subject: [PATCH 036/103] changed font size in GUI --- pyctbgui/pyctbgui/ui/signals.ui | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyctbgui/pyctbgui/ui/signals.ui b/pyctbgui/pyctbgui/ui/signals.ui index d3f1022e9..f34e357cb 100644 --- a/pyctbgui/pyctbgui/ui/signals.ui +++ b/pyctbgui/pyctbgui/ui/signals.ui @@ -6115,6 +6115,11 @@ + + + 10 + + DBit Reorder From 3297707ab7e35ace347eece2ef11b12d7d6acf56 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 11:38:56 +0200 Subject: [PATCH 037/103] clang-format with clang-format version 17 --- slsDetectorGui/include/qDefs.h | 2 +- .../slsDetectorServer_defs.h | 8 +++++--- .../eigerDetectorServer/FebControl.c | 6 +++--- .../slsDetectorServer_defs.h | 3 ++- .../slsDetectorServer_defs.h | 3 ++- .../slsDetectorServer_defs.h | 20 +++++++++++-------- .../slsDetectorServer_defs.h | 8 +++++--- .../slsDetectorServer_defs.h | 18 ++++++++++------- slsDetectorSoftware/include/sls/Result.h | 2 +- .../include/sls/detectorData.h | 6 +++--- slsReceiverSoftware/src/File.h | 4 ++-- slsReceiverSoftware/src/GeneralData.h | 4 ++-- .../tests/test-ArrangeDataBasedOnBitList.cpp | 4 ++-- slsSupportLib/include/sls/ClientSocket.h | 12 +++++------ slsSupportLib/include/sls/Timer.h | 2 +- slsSupportLib/include/sls/logger.h | 2 +- slsSupportLib/include/sls/sls_detector_defs.h | 6 +++--- slsSupportLib/src/ZmqSocket.cpp | 2 +- 18 files changed, 63 insertions(+), 49 deletions(-) diff --git a/slsDetectorGui/include/qDefs.h b/slsDetectorGui/include/qDefs.h index 018790c46..375d62d6c 100644 --- a/slsDetectorGui/include/qDefs.h +++ b/slsDetectorGui/include/qDefs.h @@ -39,7 +39,7 @@ class qDefs : public QWidget { /** * Empty Constructor */ - qDefs() {}; + qDefs(){}; static QFont GetDefaultFont() { return QFont("Cantarell", 10, QFont::Normal); diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h index 103502f1f..683338861 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h @@ -7,9 +7,11 @@ #define MIN_REQRD_VRSN_T_RD_API 0x181130 #define REQRD_FRMWR_VRSN 0x230705 -#define NUM_HARDWARE_VERSIONS (1) -#define HARDWARE_VERSION_NUMBERS {0x3f} -#define HARDWARE_VERSION_NAMES {"5.1"} +#define NUM_HARDWARE_VERSIONS (1) +#define HARDWARE_VERSION_NUMBERS \ + { 0x3f } +#define HARDWARE_VERSION_NAMES \ + { "5.1" } #define LINKED_SERVER_NAME "ctbDetectorServer" diff --git a/slsDetectorServers/eigerDetectorServer/FebControl.c b/slsDetectorServers/eigerDetectorServer/FebControl.c index d3750259a..f0b96d487 100644 --- a/slsDetectorServers/eigerDetectorServer/FebControl.c +++ b/slsDetectorServers/eigerDetectorServer/FebControl.c @@ -634,8 +634,8 @@ int Feb_Control_SetTrimbits(unsigned int *trimbits, int top) { << ((7 - i) * 4); // upper } } // end column loop i - } // end supercolumn loop sc - } // end row loop + } // end supercolumn loop sc + } // end row loop if (Feb_Control_activated) { if (!Feb_Interface_WriteMemoryInLoops(Feb_Control_leftAddress, @@ -652,7 +652,7 @@ int Feb_Control_SetTrimbits(unsigned int *trimbits, int top) { } } // end row_set loop (groups of 16 rows) - } // end l_r loop + } // end l_r loop memcpy(Feb_Control_last_downloaded_trimbits, trimbits, Feb_Control_trimbit_size * sizeof(unsigned int)); diff --git a/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h index aa87505b3..5e4a62f1b 100644 --- a/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/eigerDetectorServer/slsDetectorServer_defs.h @@ -7,7 +7,8 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x1}; -#define HARDWARE_VERSION_NAMES {"FX70T", "FX30T"} +#define HARDWARE_VERSION_NAMES \ + { "FX70T", "FX30T" } #define REQUIRED_FIRMWARE_VERSION (32) // virtual ones renamed for consistency diff --git a/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h index a696221c9..d275cb400 100644 --- a/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/gotthard2DetectorServer/slsDetectorServer_defs.h @@ -9,7 +9,8 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x2}; -#define HARDWARE_VERSION_NAMES {"1.0", "1.2"} +#define HARDWARE_VERSION_NAMES \ + { "1.0", "1.2" } #define LINKED_SERVER_NAME "gotthard2DetectorServer" diff --git a/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h index 88e30f1c4..cc2b0928b 100644 --- a/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/jungfrauDetectorServer/slsDetectorServer_defs.h @@ -8,9 +8,11 @@ #define REQRD_FRMWRE_VRSN_BOARD2 0x250209 // 1.0 pcb (version = 010) #define REQRD_FRMWRE_VRSN 0x250208 // 2.0 pcb (version = 011) -#define NUM_HARDWARE_VERSIONS (2) -#define HARDWARE_VERSION_NUMBERS {0x2, 0x3} -#define HARDWARE_VERSION_NAMES {"1.0", "2.0"} +#define NUM_HARDWARE_VERSIONS (2) +#define HARDWARE_VERSION_NUMBERS \ + { 0x2, 0x3 } +#define HARDWARE_VERSION_NAMES \ + { "1.0", "2.0" } #define ID_FILE "detid_jungfrau.txt" #define LINKED_SERVER_NAME "jungfrauDetectorServer" @@ -212,11 +214,13 @@ enum DACINDEX { enum MASTERINDEX { MASTER_HARDWARE, OW_MASTER, OW_SLAVE }; #define MASTER_NAMES "hardware", "master", "slave" -#define NUMSETTINGS (2) -#define NSPECIALDACS (3) -#define SPECIALDACINDEX {J_VREF_PRECH, J_VREF_DS, J_VREF_COMP}; -#define SPECIAL_DEFAULT_DYNAMIC_GAIN_VALS {1450, 480, 420} -#define SPECIAL_DEFAULT_DYNAMICHG0_GAIN_VALS {1550, 450, 620} +#define NUMSETTINGS (2) +#define NSPECIALDACS (3) +#define SPECIALDACINDEX {J_VREF_PRECH, J_VREF_DS, J_VREF_COMP}; +#define SPECIAL_DEFAULT_DYNAMIC_GAIN_VALS \ + { 1450, 480, 420 } +#define SPECIAL_DEFAULT_DYNAMICHG0_GAIN_VALS \ + { 1550, 450, 620 } enum NETWORKINDEX { TXN_FRAME, FLOWCTRL_10G }; enum CLKINDEX { RUN_CLK, ADC_CLK, DBIT_CLK, NUM_CLOCKS }; diff --git a/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h index 22d8e68ba..aa4133d7a 100644 --- a/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/moenchDetectorServer/slsDetectorServer_defs.h @@ -7,9 +7,11 @@ #define REQRD_FRMWRE_VRSN_BOARD2 0x444445 // 1.0 pcb (version = 010) #define REQRD_FRMWRE_VRSN 0x231026 // 2.0 pcb (version = 011) -#define NUM_HARDWARE_VERSIONS (2) -#define HARDWARE_VERSION_NUMBERS {0x2, 0x3} -#define HARDWARE_VERSION_NAMES {"1.0", "2.0"} +#define NUM_HARDWARE_VERSIONS (2) +#define HARDWARE_VERSION_NUMBERS \ + { 0x2, 0x3 } +#define HARDWARE_VERSION_NAMES \ + { "1.0", "2.0" } #define ID_FILE ("detid_moench.txt") #define LINKED_SERVER_NAME "moenchDetectorServer" diff --git a/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h index 80abbd126..b0d8c1ea5 100644 --- a/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/mythen3DetectorServer/slsDetectorServer_defs.h @@ -9,7 +9,8 @@ #define NUM_HARDWARE_VERSIONS (2) #define HARDWARE_VERSION_NUMBERS {0x0, 0x2}; -#define HARDWARE_VERSION_NAMES {"1.0", "1.2"} +#define HARDWARE_VERSION_NAMES \ + { "1.0", "1.2" } #define LINKED_SERVER_NAME "mythen3DetectorServer" @@ -129,12 +130,15 @@ enum DACINDEX { enum ADCINDEX { TEMP_FPGA }; -#define NUMSETTINGS (3) -#define NSPECIALDACS (2) -#define SPECIALDACINDEX {M_VRPREAMP, M_VRSHAPER}; -#define SPECIAL_DEFAULT_STANDARD_DAC_VALS {1100, 1280} -#define SPECIAL_DEFAULT_FAST_DAC_VALS {300, 1500} -#define SPECIAL_DEFAULT_HIGHGAIN_DAC_VALS {1300, 1100} +#define NUMSETTINGS (3) +#define NSPECIALDACS (2) +#define SPECIALDACINDEX {M_VRPREAMP, M_VRSHAPER}; +#define SPECIAL_DEFAULT_STANDARD_DAC_VALS \ + { 1100, 1280 } +#define SPECIAL_DEFAULT_FAST_DAC_VALS \ + { 300, 1500 } +#define SPECIAL_DEFAULT_HIGHGAIN_DAC_VALS \ + { 1300, 1100 } enum CLKINDEX { SYSTEM_C0, SYSTEM_C1, SYSTEM_C2, NUM_CLOCKS }; #define NUM_CLOCKS_TO_SET (1) diff --git a/slsDetectorSoftware/include/sls/Result.h b/slsDetectorSoftware/include/sls/Result.h index 479eae047..0cb937408 100644 --- a/slsDetectorSoftware/include/sls/Result.h +++ b/slsDetectorSoftware/include/sls/Result.h @@ -32,7 +32,7 @@ template > class Result { public: Result() = default; - Result(std::initializer_list list) : vec(list) {}; + Result(std::initializer_list list) : vec(list){}; /** Custom constructor from integer type to Result or Result */ template getIntArray() const { return std::array({xmin, xmax, ymin, ymax}); } diff --git a/slsSupportLib/src/ZmqSocket.cpp b/slsSupportLib/src/ZmqSocket.cpp index eed0c7221..2fafe4799 100644 --- a/slsSupportLib/src/ZmqSocket.cpp +++ b/slsSupportLib/src/ZmqSocket.cpp @@ -531,7 +531,7 @@ void ZmqSocket::PrintError() { // Nested class to do RAII handling of socket descriptors ZmqSocket::mySocketDescriptors::mySocketDescriptors(bool server) - : server(server), contextDescriptor(nullptr), socketDescriptor(nullptr) {}; + : server(server), contextDescriptor(nullptr), socketDescriptor(nullptr){}; ZmqSocket::mySocketDescriptors::~mySocketDescriptors() { Disconnect(); Close(); From 4d8bdae8363ca2a51e01be03307e8ea3a13466a7 Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Fri, 11 Apr 2025 12:38:28 +0200 Subject: [PATCH 038/103] updated update_image_size in xilinx --- slsReceiverSoftware/src/GeneralData.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 9f3b43429..17a4ea7e5 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -60,8 +60,8 @@ class GeneralData { slsDetectorDefs::frameDiscardPolicy frameDiscardMode{ slsDetectorDefs::NO_DISCARD}; - GeneralData(){}; - virtual ~GeneralData(){}; + GeneralData() {}; + virtual ~GeneralData() {}; // Returns the pixel depth in byte, 4 bits being 0.5 byte float GetPixelDepth() { return float(dynamicRange) / 8; } @@ -569,6 +569,7 @@ class XilinxChipTestBoardData : public GeneralData { nDigitalBytes = 0; nTransceiverBytes = 0; int nAnalogChans = 0, nDigitalChans = 0, nTransceiverChans = 0; + uint64_t digital_bytes_reserved = 0; // analog channels (normal, analog/digital readout) if (readoutType == slsDetectorDefs::ANALOG_ONLY || @@ -586,7 +587,11 @@ class XilinxChipTestBoardData : public GeneralData { readoutType == slsDetectorDefs::ANALOG_AND_DIGITAL || readoutType == slsDetectorDefs::DIGITAL_AND_TRANSCEIVER) { nDigitalChans = NCHAN_DIGITAL; - nDigitalBytes = (sizeof(uint64_t) * nDigitalSamples); + uint32_t num_bytes_per_bit = (nDigitalSamples % 8 == 0) + ? nDigitalSamples / 8 + : nDigitalSamples / 8 + 1; + digital_bytes_reserved = 64 * num_bytes_per_bit; + nDigitalBytes = sizeof(uint64_t) * nDigitalSamples; LOG(logDEBUG1) << "Number of Digital Channels:" << nDigitalChans << " Databytes: " << nDigitalBytes; } @@ -604,7 +609,7 @@ class XilinxChipTestBoardData : public GeneralData { } nPixelsX = nAnalogChans + nDigitalChans + nTransceiverChans; - imageSize = nAnalogBytes + nDigitalBytes + nTransceiverBytes; + imageSize = nAnalogBytes + digital_bytes_reserved + nTransceiverBytes; packetsPerFrame = ceil((double)imageSize / (double)dataSize); LOG(logDEBUG1) << "Total Number of Channels:" << nPixelsX From da760b2b934af55e5e0f691591245af66c59e773 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Tue, 22 Apr 2025 14:00:45 +0200 Subject: [PATCH 039/103] version number automated for python build --- conda-recipes/build_conda.sh | 8 ++++++++ conda-recipes/main-library/meta.yaml | 6 +++--- conda-recipes/python-client/meta.yaml | 4 ++-- pyproject.toml | 18 +++++++++--------- update_version.py | 22 +++++++++++++++++++++- 5 files changed, 43 insertions(+), 15 deletions(-) create mode 100755 conda-recipes/build_conda.sh diff --git a/conda-recipes/build_conda.sh b/conda-recipes/build_conda.sh new file mode 100755 index 000000000..6915f89a9 --- /dev/null +++ b/conda-recipes/build_conda.sh @@ -0,0 +1,8 @@ + +SCRIPTDIR=$(cd "$(dirname "$0")" && pwd) +VERSION=$(cat $SCRIPTDIR/../VERSION) +export VERSION + +echo "building conda package version: $VERSION" + +conda build . diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 2ddc76a6f..232f89b20 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -1,8 +1,8 @@ +#{% set version = load_file('VERSION').strip %} package: name: sls_detector_software - version: 2025.3.19 - - + version: {{ environ.get('VERSION') }} #2025.3.19 + source: path: ../.. diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index 3b710151c..c5e2cbb38 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,7 +1,7 @@ + package: name: slsdet - version: 2025.3.19 #TODO! how to not duplicate this? - + version: 0.0.0 source: path: ../.. diff --git a/pyproject.toml b/pyproject.toml index 3d128f18e..5e11fdc45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,27 +1,27 @@ [build-system] -requires = ["scikit-build-core>=0.10", "pybind11", "numpy"] +requires = [ "scikit-build-core>=0.10", "pybind11", "numpy",] build-backend = "scikit_build_core.build" [project] name = "slsdet" version = "2025.3.19" - [tool.cibuildwheel] before-all = "uname -a" -[tool.scikit-build] -build.verbose = true -cmake.build-type = "Release" -install.components = ["python"] +[tool.scikit-build.build] +verbose = true +[tool.scikit-build.cmake] +build-type = "Release" + +[tool.scikit-build.install] +components = [ "python",] [tool.scikit-build.cmake.define] -#Only build the control software and python ext SLS_USE_RECEIVER = "OFF" SLS_USE_RECEIVER_BINARIES = "OFF" SLS_USE_TEXTCLIENT = "OFF" SLS_BUILD_SHARED_LIBRARIES = "OFF" - SLS_USE_PYTHON = "ON" -SLS_INSTALL_PYTHONEXT = "ON" \ No newline at end of file +SLS_INSTALL_PYTHONEXT = "ON" diff --git a/update_version.py b/update_version.py index c074ae542..f5c28c5af 100644 --- a/update_version.py +++ b/update_version.py @@ -6,6 +6,10 @@ Script to update VERSION file with semantic versioning if provided as an argumen import sys import re +import os +import toml +import yaml +from jinja2 import Template, Undefined def get_version(): @@ -28,9 +32,25 @@ def write_version_to_file(version): version_file.write(version) print(f"Version {version} written to VERSION file.") +def define_environment_variable(version): + os.environ["VERSION"] = version + +def update_pyproject_toml_file(version): + pyproject = toml.load("pyproject.toml") + pyproject["project"]["version"] = version + toml.dump(pyproject, open("pyproject.toml", "w")) #write back + print(f"Version in pyproject.toml set to {version}") + +class NullUndefined(Undefined): + def __getattr__(self, key): + return '' + + # Main script if __name__ == "__main__": version = get_version() - write_version_to_file(version) \ No newline at end of file + write_version_to_file(version) + define_environment_variable(version) + update_pyproject_toml_file(version) From 8fe4a78febcf13e10653270d9fdfa4ad40db9b5f Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Tue, 22 Apr 2025 15:43:01 +0200 Subject: [PATCH 040/103] mistakenly set version back to 0.0.0 --- conda-recipes/python-client/meta.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index c5e2cbb38..7daa969a6 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,7 +1,8 @@ package: name: slsdet - version: 0.0.0 + version: {{ environ.get('VERSION') }} #2025.3.19 + source: path: ../.. From fca31cc4322fa80f33c458bf5f711c0c017babce Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Tue, 22 Apr 2025 17:05:06 +0200 Subject: [PATCH 041/103] updated github workflow scripts to support automatic version numbering with environment variable --- .github/workflows/conda_library.yaml | 2 +- .github/workflows/conda_python.yaml | 2 +- conda-recipes/build_conda.sh | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/conda_library.yaml b/.github/workflows/conda_library.yaml index 23f94d467..807d7872b 100644 --- a/.github/workflows/conda_library.yaml +++ b/.github/workflows/conda_library.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: conda build conda-recipes/main-library --output-folder build_output + run: ./conda-recipes/build_conda.sh build_output main-library - name: Upload all Conda packages uses: actions/upload-artifact@v4 diff --git a/.github/workflows/conda_python.yaml b/.github/workflows/conda_python.yaml index d482b8e6f..df67552e1 100644 --- a/.github/workflows/conda_python.yaml +++ b/.github/workflows/conda_python.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: conda build conda-recipes/python-client --output-folder build_output + run: ./conda-recipes/build_conda.sh build_output python-client - name: Upload all Conda packages uses: actions/upload-artifact@v4 diff --git a/conda-recipes/build_conda.sh b/conda-recipes/build_conda.sh index 6915f89a9..7e6ab3f46 100755 --- a/conda-recipes/build_conda.sh +++ b/conda-recipes/build_conda.sh @@ -3,6 +3,21 @@ SCRIPTDIR=$(cd "$(dirname "$0")" && pwd) VERSION=$(cat $SCRIPTDIR/../VERSION) export VERSION -echo "building conda package version: $VERSION" +if [ -z "$1" ] +then + output_dir=$CONDA_PREFIX/conda-bld +else + output_dir=$1 +fi -conda build . +if [ -z "$2" ] +then + recipe=$SCRIPTDIR +else + recipe=$SCRIPTDIR/$2 +fi + +echo "building conda package version: $VERSION using recipe $recipe and writing output to $output_dir" + + +conda build $recipe --output-folder $output_dir From 497c3abfc24af91d3cc224db31b91a6324eda6f0 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 23 Apr 2025 14:26:26 +0200 Subject: [PATCH 042/103] managed to load VERSION file in yaml file - simplifies things --- .github/workflows/conda_python.yaml | 2 +- conda-recipes/build_conda.sh | 23 ----------------------- conda-recipes/main-library/meta.yaml | 10 +++++----- conda-recipes/python-client/meta.yaml | 10 +++++----- pyproject.toml | 2 +- update_version.py | 14 -------------- 6 files changed, 12 insertions(+), 49 deletions(-) delete mode 100755 conda-recipes/build_conda.sh diff --git a/.github/workflows/conda_python.yaml b/.github/workflows/conda_python.yaml index df67552e1..778b18ad7 100644 --- a/.github/workflows/conda_python.yaml +++ b/.github/workflows/conda_python.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: ./conda-recipes/build_conda.sh build_output python-client + run: conda build ./conda-recipes/python-client --build-folder build_output - name: Upload all Conda packages uses: actions/upload-artifact@v4 diff --git a/conda-recipes/build_conda.sh b/conda-recipes/build_conda.sh deleted file mode 100755 index 7e6ab3f46..000000000 --- a/conda-recipes/build_conda.sh +++ /dev/null @@ -1,23 +0,0 @@ - -SCRIPTDIR=$(cd "$(dirname "$0")" && pwd) -VERSION=$(cat $SCRIPTDIR/../VERSION) -export VERSION - -if [ -z "$1" ] -then - output_dir=$CONDA_PREFIX/conda-bld -else - output_dir=$1 -fi - -if [ -z "$2" ] -then - recipe=$SCRIPTDIR -else - recipe=$SCRIPTDIR/$2 -fi - -echo "building conda package version: $VERSION using recipe $recipe and writing output to $output_dir" - - -conda build $recipe --output-folder $output_dir diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 232f89b20..2c2c88058 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -1,11 +1,11 @@ -#{% set version = load_file('VERSION').strip %} -package: - name: sls_detector_software - version: {{ environ.get('VERSION') }} #2025.3.19 - source: path: ../.. +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+)').group(1) %} +package: + name: sls_detector_software + version: {{ version }} #2025.3.19 + build: number: 0 binary_relocation: True diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index 7daa969a6..cf4f3aa7b 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,10 +1,11 @@ +source: + path: ../.. + +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+)').group(1) %} package: name: slsdet - version: {{ environ.get('VERSION') }} #2025.3.19 - -source: - path: ../.. + version: {{ version }} # build: number: 0 @@ -18,7 +19,6 @@ requirements: - {{ stdlib("c") }} - {{ compiler('cxx') }} - host: - cmake - ninja diff --git a/pyproject.toml b/pyproject.toml index 5e11fdc45..776657c41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "slsdet" -version = "2025.3.19" +version = "0.0.0" [tool.cibuildwheel] before-all = "uname -a" diff --git a/update_version.py b/update_version.py index f5c28c5af..507857133 100644 --- a/update_version.py +++ b/update_version.py @@ -6,10 +6,7 @@ Script to update VERSION file with semantic versioning if provided as an argumen import sys import re -import os import toml -import yaml -from jinja2 import Template, Undefined def get_version(): @@ -26,31 +23,20 @@ def get_version(): return version - def write_version_to_file(version): with open("VERSION", "w") as version_file: version_file.write(version) print(f"Version {version} written to VERSION file.") -def define_environment_variable(version): - os.environ["VERSION"] = version - def update_pyproject_toml_file(version): pyproject = toml.load("pyproject.toml") pyproject["project"]["version"] = version toml.dump(pyproject, open("pyproject.toml", "w")) #write back print(f"Version in pyproject.toml set to {version}") -class NullUndefined(Undefined): - def __getattr__(self, key): - return '' - - - # Main script if __name__ == "__main__": version = get_version() write_version_to_file(version) - define_environment_variable(version) update_pyproject_toml_file(version) From 2571397c701515e3cc829d6473f555be933c9896 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 23 Apr 2025 14:38:51 +0200 Subject: [PATCH 043/103] saving changes in git workflow failed --- .github/workflows/conda_library.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conda_library.yaml b/.github/workflows/conda_library.yaml index 807d7872b..9bde3bb31 100644 --- a/.github/workflows/conda_library.yaml +++ b/.github/workflows/conda_library.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: ./conda-recipes/build_conda.sh build_output main-library + run: conda build ./conda-recipes/main-library --build-folder build_output - name: Upload all Conda packages uses: actions/upload-artifact@v4 From 99735c3ee518da7a111a9f7489ace0f0830cf5c4 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 23 Apr 2025 14:46:12 +0200 Subject: [PATCH 044/103] got typo in github workflow --- .github/workflows/conda_library.yaml | 2 +- .github/workflows/conda_python.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conda_library.yaml b/.github/workflows/conda_library.yaml index 9bde3bb31..23f94d467 100644 --- a/.github/workflows/conda_library.yaml +++ b/.github/workflows/conda_library.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: conda build ./conda-recipes/main-library --build-folder build_output + run: conda build conda-recipes/main-library --output-folder build_output - name: Upload all Conda packages uses: actions/upload-artifact@v4 diff --git a/.github/workflows/conda_python.yaml b/.github/workflows/conda_python.yaml index 778b18ad7..5dad7745a 100644 --- a/.github/workflows/conda_python.yaml +++ b/.github/workflows/conda_python.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: conda build ./conda-recipes/python-client --build-folder build_output + run: conda build ./conda-recipes/python-client --output-folder build_output - name: Upload all Conda packages uses: actions/upload-artifact@v4 From bace9edf8934a898a4fdba9769f2f68f72669c7f Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 23 Apr 2025 17:09:06 +0200 Subject: [PATCH 045/103] updatet regex pattern to support postfix --- .github/workflows/conda_python.yaml | 2 +- conda-recipes/main-library/meta.yaml | 2 +- conda-recipes/python-client/meta.yaml | 3 +-- update_version.py | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/conda_python.yaml b/.github/workflows/conda_python.yaml index 5dad7745a..4b12cb3ff 100644 --- a/.github/workflows/conda_python.yaml +++ b/.github/workflows/conda_python.yaml @@ -33,7 +33,7 @@ jobs: run: conda config --set anaconda_upload no - name: Build - run: conda build ./conda-recipes/python-client --output-folder build_output + run: conda build conda-recipes/python-client --output-folder build_output - name: Upload all Conda packages uses: actions/upload-artifact@v4 diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 2c2c88058..3a45167cc 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -1,7 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+)').group(1) %} +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+(?:[\.\-][\.\w\-]+)?)').group(1) %} package: name: sls_detector_software version: {{ version }} #2025.3.19 diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index cf4f3aa7b..03c4b4436 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,8 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+)').group(1) %} - +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+(?:[\.\w\-]+)?)').group(1) %} package: name: slsdet version: {{ version }} # diff --git a/update_version.py b/update_version.py index 507857133..c72099b3c 100644 --- a/update_version.py +++ b/update_version.py @@ -17,8 +17,8 @@ def get_version(): version = sys.argv[1] # Validate that the version argument matches semantic versioning format (X.Y.Z) - if not re.match(r'^\d+\.\d+\.\d+$', version): - print("Error: Version argument must be in semantic versioning format (X.Y.Z)") + if not re.match(r'^\d+\.\d+\.\d+(?:[\-\.][\.\w\-]+)?+$', version): + print("Error: Version argument must be in semantic versioning format (X.Y.Z[./-][postfix])") sys.exit(1) return version From 0e4cb7cbcd8482b872a24d46c3111637d4b5369e Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Thu, 24 Apr 2025 08:45:18 +0200 Subject: [PATCH 046/103] normalized version to PEP 440 specification in update_version.py --- conda-recipes/main-library/meta.yaml | 4 +--- conda-recipes/python-client/meta.yaml | 2 +- update_version.py | 14 ++++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 3a45167cc..bfc176363 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -1,7 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+(?:[\.\-][\.\w\-]+)?)').group(1) %} +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*+(?:[\+\w\.]+))').group(1) %} package: name: sls_detector_software version: {{ version }} #2025.3.19 @@ -49,8 +49,6 @@ outputs: - libgcc-ng - - - name: slsdetgui script: copy_gui.sh requirements: diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index 03c4b4436..dd9ea031c 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,7 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+\.\d+\.\d+(?:[\.\w\-]+)?)').group(1) %} +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*+(?:[\+\w\.]+))').group(1) %} package: name: slsdet version: {{ version }} # diff --git a/update_version.py b/update_version.py index c72099b3c..914797a1d 100644 --- a/update_version.py +++ b/update_version.py @@ -7,6 +7,7 @@ Script to update VERSION file with semantic versioning if provided as an argumen import sys import re import toml +from packaging.version import Version, InvalidVersion def get_version(): @@ -16,21 +17,22 @@ def get_version(): version = sys.argv[1] - # Validate that the version argument matches semantic versioning format (X.Y.Z) - if not re.match(r'^\d+\.\d+\.\d+(?:[\-\.][\.\w\-]+)?+$', version): - print("Error: Version argument must be in semantic versioning format (X.Y.Z[./-][postfix])") + try: + v = Version(version) # normalize according to PEP 440 specification + return v + except InvalidVersion as e: + print(f"Invalid version {version}. Version format must follow semantic versioning format of python PEP 440 version identification specification.") sys.exit(1) - return version def write_version_to_file(version): with open("VERSION", "w") as version_file: - version_file.write(version) + version_file.write(str(version)) print(f"Version {version} written to VERSION file.") def update_pyproject_toml_file(version): pyproject = toml.load("pyproject.toml") - pyproject["project"]["version"] = version + pyproject["project"]["version"] = str(version) toml.dump(pyproject, open("pyproject.toml", "w")) #write back print(f"Version in pyproject.toml set to {version}") From c3f1d050338ee7451131c7ff2e82ab1126ff1d40 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Thu, 24 Apr 2025 09:16:32 +0200 Subject: [PATCH 047/103] bug did not support version 0.0.0 --- conda-recipes/main-library/meta.yaml | 2 +- conda-recipes/python-client/meta.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index bfc176363..5ac6967d2 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -1,7 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*+(?:[\+\w\.]+))').group(1) %} +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*(?:[\+\w\.]+))').group(1) %} package: name: sls_detector_software version: {{ version }} #2025.3.19 diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index dd9ea031c..d3fed0b9a 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -1,7 +1,7 @@ source: path: ../.. -{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*+(?:[\+\w\.]+))').group(1) %} +{% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*(?:[\+\w\.]+))').group(1) %} package: name: slsdet version: {{ version }} # From 4d7d3c9138b9a2a4520e06b44e96a39dc244733d Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 25 Apr 2025 09:09:49 +0200 Subject: [PATCH 048/103] upgrading to c++17 from c++11 and patch command has to be found before applying patch on libzmq (#1195) --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1d035d76..6b3127da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,10 @@ endif() # Patch is applied in the FetchContent_Declare set(SLS_LIBZMQ_VERSION "4.3.4") +find_program(PATCH_EXECUTABLE patch) +if(NOT PATCH_EXECUTABLE) + message(FATAL_ERROR "The 'patch' tool is required for patching lib zeromq. Please install it.") +endif() if(SLS_FETCH_ZMQ_FROM_GITHUB) # Opt in to pull down a zmq version from github instead of @@ -216,7 +220,7 @@ endif() # to control options for the libraries if(NOT TARGET slsProjectOptions) add_library(slsProjectOptions INTERFACE) - target_compile_features(slsProjectOptions INTERFACE cxx_std_11) + target_compile_features(slsProjectOptions INTERFACE cxx_std_17) endif() if (NOT TARGET slsProjectWarnings) From 5fa2402ec53ad6937517e5576132ea1466f8a40d Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 25 Apr 2025 10:14:15 +0200 Subject: [PATCH 049/103] Dev/allow localhost for virtual tests (#1190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove the check for localhost being used in rx_hostname for python test for simulators, run rx_arping test only if hostname is not 'localhost' * fix tests for fpath: cannot set back to empty anymore (empty is default) * default rx_hostname arg = localhost, and default settings path =../../settingsdir * changed virtual tests script for better printout on exceptions * fix for catching generaltests exceptions and exiting instead of continuing * fix minor * fixed shared memeory tests to include current env and fixed prints for errors --------- Co-authored-by: Erik Fröjdh --- .../tests/Caller/test-Caller-rx.cpp | 39 ++--- .../tests/test-SharedMemory.cpp | 25 +++- tests/scripts/test_simulators.py | 138 +++++++++--------- 3 files changed, 114 insertions(+), 88 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp index 508abc59d..fcf0de97d 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-rx.cpp @@ -445,23 +445,25 @@ TEST_CASE("rx_arping", "[.cmdcall][.rx]") { Detector det; Caller caller(&det); auto prev_val = det.getRxArping(); - { - std::ostringstream oss; - caller.call("rx_arping", {"1"}, -1, PUT, oss); - REQUIRE(oss.str() == "rx_arping 1\n"); - } - { - std::ostringstream oss; - caller.call("rx_arping", {}, -1, GET, oss); - REQUIRE(oss.str() == "rx_arping 1\n"); - } - { - std::ostringstream oss; - caller.call("rx_arping", {"0"}, -1, PUT, oss); - REQUIRE(oss.str() == "rx_arping 0\n"); - } - for (int i = 0; i != det.size(); ++i) { - det.setRxArping(prev_val[i], {i}); + if (det.getDestinationUDPIP()[0].str() != "127.0.0.1") { + { + std::ostringstream oss; + caller.call("rx_arping", {"1"}, -1, PUT, oss); + REQUIRE(oss.str() == "rx_arping 1\n"); + } + { + std::ostringstream oss; + caller.call("rx_arping", {}, -1, GET, oss); + REQUIRE(oss.str() == "rx_arping 1\n"); + } + { + std::ostringstream oss; + caller.call("rx_arping", {"0"}, -1, PUT, oss); + REQUIRE(oss.str() == "rx_arping 0\n"); + } + for (int i = 0; i != det.size(); ++i) { + det.setRxArping(prev_val[i], {i}); + } } } @@ -583,6 +585,9 @@ TEST_CASE("fpath", "[.cmdcall]") { REQUIRE(oss.str() == "fpath /tmp\n"); } for (int i = 0; i != det.size(); ++i) { + if (prev_val[i].empty()) { + continue; + } det.setFilePath(prev_val[i], {i}); } } diff --git a/slsDetectorSoftware/tests/test-SharedMemory.cpp b/slsDetectorSoftware/tests/test-SharedMemory.cpp index 5ba3c20a2..4dff6d5d7 100644 --- a/slsDetectorSoftware/tests/test-SharedMemory.cpp +++ b/slsDetectorSoftware/tests/test-SharedMemory.cpp @@ -18,11 +18,16 @@ struct Data { constexpr int shm_id = 10; TEST_CASE("Create SharedMemory read and write", "[detector]") { + const char *env_p = std::getenv("SLSDETNAME"); + std::string env_name = env_p ? ("_" + std::string(env_p)) : ""; SharedMemory shm(shm_id, -1); + if (shm.exists()) { + shm.removeSharedMemory(); + } shm.createSharedMemory(); CHECK(shm.getName() == std::string("/slsDetectorPackage_detector_") + - std::to_string(shm_id)); + std::to_string(shm_id) + env_name); shm()->x = 3; shm()->y = 5.7; @@ -90,10 +95,12 @@ TEST_CASE("Open two shared memories to the same place", "[detector]") { } TEST_CASE("Move SharedMemory", "[detector]") { + const char *env_p = std::getenv("SLSDETNAME"); + std::string env_name = env_p ? ("_" + std::string(env_p)) : ""; SharedMemory shm(shm_id, -1); CHECK(shm.getName() == std::string("/slsDetectorPackage_detector_") + - std::to_string(shm_id)); + std::to_string(shm_id) + env_name); shm.createSharedMemory(); shm()->x = 9; @@ -104,15 +111,19 @@ TEST_CASE("Move SharedMemory", "[detector]") { REQUIRE_THROWS( shm()); // trying to access should throw instead of returning a nullptr CHECK(shm2.getName() == std::string("/slsDetectorPackage_detector_") + - std::to_string(shm_id)); + std::to_string(shm_id) + env_name); shm2.removeSharedMemory(); } TEST_CASE("Create several shared memories", "[detector]") { + const char *env_p = std::getenv("SLSDETNAME"); + std::string env_name = env_p ? ("_" + std::string(env_p)) : ""; + constexpr int N = 5; std::vector> v; v.reserve(N); for (int i = 0; i != N; ++i) { + std::cout << "i:" << i << std::endl; v.emplace_back(shm_id + i, -1); CHECK(v[i].exists() == false); v[i].createSharedMemory(); @@ -123,7 +134,7 @@ TEST_CASE("Create several shared memories", "[detector]") { for (int i = 0; i != N; ++i) { CHECK(*v[i]() == i); CHECK(v[i].getName() == std::string("/slsDetectorPackage_detector_") + - std::to_string(i + shm_id)); + std::to_string(i + shm_id) + env_name); } for (int i = 0; i != N; ++i) { @@ -133,8 +144,12 @@ TEST_CASE("Create several shared memories", "[detector]") { } TEST_CASE("Create create a shared memory with a tag") { + const char *env_p = std::getenv("SLSDETNAME"); + std::string env_name = env_p ? ("_" + std::string(env_p)) : ""; + SharedMemory shm(0, -1, "ctbdacs"); - REQUIRE(shm.getName() == "/slsDetectorPackage_detector_0_ctbdacs"); + REQUIRE(shm.getName() == + "/slsDetectorPackage_detector_0" + env_name + "_ctbdacs"); } TEST_CASE("Create create a shared memory with a tag when SLSDETNAME is set") { diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index ce7fbd420..ea580c5e6 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -4,7 +4,7 @@ This file is used to start up simulators, receivers and run all the tests on them and finally kill the simulators and receivers. ''' import argparse -import os, sys, subprocess, time, colorama, signal +import os, sys, subprocess, time, colorama from colorama import Fore from slsdet import Detector, detectorType, detectorSettings @@ -23,23 +23,9 @@ def Log(color, message): def checkIfProcessRunning(processName): - cmd = "ps -ef | grep " + processName - print(cmd) - res=subprocess.getoutput(cmd) - print(res) - # eg. of output - #l_user 250506 243295 0 14:38 pts/5 00:00:00 /bin/sh -c ps -ef | grep slsReceiver - #l_user 250508 250506 0 14:38 pts/5 00:00:00 grep slsReceiver - - print('how many') - cmd = "ps -ef | grep " + processName + " | wc -l" - print(cmd) - res=subprocess.getoutput(cmd) - print(res) - - if res == '2': - return False - return True + cmd = f"pgrep -f {processName}" + res = subprocess.getoutput(cmd) + return bool(res.strip()) def killProcess(name): @@ -52,7 +38,7 @@ def killProcess(name): print('process not running : ' + name) -def killAllStaleProcesses(): +def killAllStaleProcesses(fp): killProcess('eigerDetectorServer_virtual') killProcess('jungfrauDetectorServer_virtual') killProcess('mythen3DetectorServer_virtual') @@ -62,9 +48,9 @@ def killAllStaleProcesses(): killProcess('xilinx_ctbDetectorServer_virtual') killProcess('slsReceiver') killProcess('slsMultiReceiver') - cleanSharedmemory() + cleanSharedmemory(fp) -def cleanup(name): +def cleanup(name, fp): ''' kill both servers, receivers and clean shared memory ''' @@ -72,9 +58,9 @@ def cleanup(name): killProcess(name + 'DetectorServer_virtual') killProcess('slsReceiver') killProcess('slsMultiReceiver') - cleanSharedmemory() + cleanSharedmemory(fp) -def cleanSharedmemory(): +def cleanSharedmemory(fp): Log(Fore.GREEN, 'Cleaning up shared memory...') try: p = subprocess.run(['sls_detector_get', 'free'], stdout=fp, stderr=fp) @@ -87,8 +73,8 @@ def startProcessInBackground(name): # in background and dont print output p = subprocess.Popen(name.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, restore_signals=False) Log(Fore.GREEN, 'Starting up ' + name + ' ...') - except: - Log(Fore.RED, 'Could not start ' + name) + except Exception as e: + Log(Fore.RED, f'Could not start {name}:{e}') raise def startServer(name): @@ -139,14 +125,19 @@ def loadConfig(name, rx_hostname, settingsdir): def startCmdTests(name, fp, fname): Log(Fore.GREEN, 'Cmd Tests for ' + name) cmd = 'tests --abort [.cmdcall] -s -o ' + fname - p = subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) - p.check_returncode() + try: + subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) + except subprocess.CalledProcessError as e: + pass with open (fname, 'r') as f: for line in f: if "FAILED" in line: msg = 'Cmd tests failed for ' + name + '!!!' + sys.stdout = original_stdout Log(Fore.RED, msg) + Log(Fore.RED, line) + sys.stdout = fp raise Exception(msg) Log(Fore.GREEN, 'Cmd Tests successful for ' + name) @@ -154,14 +145,18 @@ def startCmdTests(name, fp, fname): def startGeneralTests(fp, fname): Log(Fore.GREEN, 'General Tests') cmd = 'tests --abort -s -o ' + fname - p = subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) - p.check_returncode() + try: + subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) + except subprocess.CalledProcessError as e: + pass with open (fname, 'r') as f: for line in f: if "FAILED" in line: msg = 'General tests failed !!!' - Log(Fore.RED, msg) + sys.stdout = original_stdout + Log(Fore.RED, msg + '\n' + line) + sys.stdout = fp raise Exception(msg) Log(Fore.GREEN, 'General Tests successful') @@ -170,12 +165,10 @@ def startGeneralTests(fp, fname): # parse cmd line for rx_hostname and settingspath using the argparse library parser = argparse.ArgumentParser(description = 'automated tests with the virtual detector servers') -parser.add_argument('rx_hostname', help = 'hostname/ip of the current machine') -parser.add_argument('settingspath', help = 'Relative or absolut path to the settingspath') +parser.add_argument('rx_hostname', nargs='?', default='localhost', help = 'hostname/ip of the current machine') +parser.add_argument('settingspath', nargs='?', default='../../settingsdir', help = 'Relative or absolut path to the settingspath') parser.add_argument('-s', '--servers', help='Detector servers to run', nargs='*') args = parser.parse_args() -if args.rx_hostname == 'localhost': - raise RuntimeException('Cannot use localhost for rx_hostname for the tests (fails for rx_arping for eg.)') if args.servers is None: servers = [ @@ -203,46 +196,59 @@ Log(Fore.BLUE, '\nLog File: ' + fname) with open(fname, 'w') as fp: + + # general tests file_results = prefix_fname + '_results_general.txt' Log(Fore.BLUE, 'General tests (results: ' + file_results + ')') sys.stdout = fp sys.stderr = fp Log(Fore.BLUE, 'General tests (results: ' + file_results + ')') - startGeneralTests(fp, file_results) - killAllStaleProcesses() + try: + startGeneralTests(fp, file_results) + killAllStaleProcesses(fp) - for server in servers: - try: - # print to terminal for progress - sys.stdout = original_stdout - sys.stderr = original_stderr - file_results = prefix_fname + '_results_cmd_' + server + '.txt' - Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') - sys.stdout = fp - sys.stderr = fp - Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') - - # cmd tests for det - cleanup(server) - startServer(server) - startReceiver(server) - loadConfig(server, args.rx_hostname, args.settingspath) - startCmdTests(server, fp, file_results) - cleanup(server) - except: - Log(Fore.RED, 'Exception caught. Cleaning up.') - cleanup(server) - sys.stdout = original_stdout - sys.stderr = original_stderr - Log(Fore.RED, 'Cmd tests failed for ' + server + '!!!') - raise + testError = False + for server in servers: + try: + # print to terminal for progress + sys.stdout = original_stdout + sys.stderr = original_stderr + file_results = prefix_fname + '_results_cmd_' + server + '.txt' + Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') + sys.stdout = fp + sys.stderr = fp + Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') + + # cmd tests for det + cleanup(server, fp) + startServer(server) + startReceiver(server) + loadConfig(server, args.rx_hostname, args.settingspath) + startCmdTests(server, fp, file_results) + cleanup(server, fp) + + except Exception as e: + # redirect to terminal + sys.stdout = original_stdout + sys.stderr = original_stderr + Log(Fore.RED, f'Exception caught while testing {server}. Cleaning up...') + testError = True + break + + # redirect to terminal + sys.stdout = original_stdout + sys.stderr = original_stderr + if not testError: + Log(Fore.GREEN, 'Passed all tests for virtual detectors \n' + str(servers)) - Log(Fore.GREEN, 'Passed all tests for virtual detectors \n' + str(servers)) + except Exception as e: + # redirect to terminal + sys.stdout = original_stdout + sys.stderr = original_stderr + Log(Fore.RED, f'Exception caught with general testing. Cleaning up...') + cleanSharedmemory(sys.stdout) + -# redirect to terminal -sys.stdout = original_stdout -sys.stderr = original_stderr -Log(Fore.GREEN, 'Passed all tests for virtual detectors \n' + str(servers) + '\nYayyyy! :) ') \ No newline at end of file From 2815913d1051635182d3a1332163c80f077b85e1 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Fri, 25 Apr 2025 10:48:16 +0200 Subject: [PATCH 050/103] added regex pattern matching to version in toml file --- conda-recipes/main-library/meta.yaml | 2 +- conda-recipes/python-client/meta.yaml | 2 +- pyproject.toml | 8 +++++++- update_version.py | 17 +++++++---------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 5ac6967d2..8b11b4536 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -4,7 +4,7 @@ source: {% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*(?:[\+\w\.]+))').group(1) %} package: name: sls_detector_software - version: {{ version }} #2025.3.19 + version: {{ version }} build: number: 0 diff --git a/conda-recipes/python-client/meta.yaml b/conda-recipes/python-client/meta.yaml index d3fed0b9a..c86f401ef 100644 --- a/conda-recipes/python-client/meta.yaml +++ b/conda-recipes/python-client/meta.yaml @@ -4,7 +4,7 @@ source: {% set version = load_file_regex(load_file = 'VERSION', regex_pattern = '(\d+(?:\.\d+)*(?:[\+\w\.]+))').group(1) %} package: name: slsdet - version: {{ version }} # + version: {{ version }} build: number: 0 diff --git a/pyproject.toml b/pyproject.toml index 776657c41..8deb25dde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,16 @@ +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.regex" +input = "VERSION" +regex = '^(?P\d+(?:\.\d+)*(?:[\.\+\w]+)?)$' +result = "{version}" + [build-system] requires = [ "scikit-build-core>=0.10", "pybind11", "numpy",] build-backend = "scikit_build_core.build" [project] name = "slsdet" -version = "0.0.0" +dynamic = ["version"] [tool.cibuildwheel] before-all = "uname -a" diff --git a/update_version.py b/update_version.py index 914797a1d..2b5609198 100644 --- a/update_version.py +++ b/update_version.py @@ -5,10 +5,13 @@ Script to update VERSION file with semantic versioning if provided as an argumen """ import sys -import re -import toml +import os + from packaging.version import Version, InvalidVersion + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + def get_version(): # Check at least one argument is passed @@ -26,19 +29,13 @@ def get_version(): def write_version_to_file(version): - with open("VERSION", "w") as version_file: + version_file_path = os.path.join(SCRIPT_DIR, "VERSION") + with open(version_file_path, "w") as version_file: version_file.write(str(version)) print(f"Version {version} written to VERSION file.") -def update_pyproject_toml_file(version): - pyproject = toml.load("pyproject.toml") - pyproject["project"]["version"] = str(version) - toml.dump(pyproject, open("pyproject.toml", "w")) #write back - print(f"Version in pyproject.toml set to {version}") - # Main script if __name__ == "__main__": version = get_version() write_version_to_file(version) - update_pyproject_toml_file(version) From 625f4353fbc3bcd4102022d3050e93767d3cca3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Fri, 25 Apr 2025 12:04:45 +0200 Subject: [PATCH 051/103] Dev/gitea docker (#1194) * gitea workflows for RH8 and RH9 * using our docker images --- .gitea/workflows/rh8-native.yml | 29 +++++++++++++++++++++++++++++ .gitea/workflows/rh9-native.yml | 27 +++++++++++++++++++++++++++ CMakeLists.txt | 3 ++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/rh8-native.yml create mode 100644 .gitea/workflows/rh9-native.yml diff --git a/.gitea/workflows/rh8-native.yml b/.gitea/workflows/rh8-native.yml new file mode 100644 index 000000000..51d754125 --- /dev/null +++ b/.gitea/workflows/rh8-native.yml @@ -0,0 +1,29 @@ +name: Build on RHEL8 + +on: + push: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: "ubuntu-latest" + container: + image: gitea.psi.ch/detectors/rhel8-detectors-dev + steps: + - name: Clone repository + run: | + echo Cloning ${{ github.ref_name }} + git clone https://${{secrets.GITHUB_TOKEN}}@gitea.psi.ch/${{ github.repository }}.git --branch=${{ github.ref_name }} . + + - name: Build library + run: | + mkdir build && cd build + cmake .. -DSLS_USE_PYTHON=ON -DSLS_USE_TESTS=ON + make -j 2 + + - name: C++ unit tests + working-directory: ${{gitea.workspace}}/build + run: ctest \ No newline at end of file diff --git a/.gitea/workflows/rh9-native.yml b/.gitea/workflows/rh9-native.yml new file mode 100644 index 000000000..890b09edf --- /dev/null +++ b/.gitea/workflows/rh9-native.yml @@ -0,0 +1,27 @@ +name: Build on RHEL9 + +on: + push: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: "ubuntu-latest" + container: + image: gitea.psi.ch/detectors/rhel9-detectors-dev + steps: + - uses: actions/checkout@v4 + + + - name: Build library + run: | + mkdir build && cd build + cmake .. -DSLS_USE_PYTHON=ON -DSLS_USE_TESTS=ON + make -j 2 + + - name: C++ unit tests + working-directory: ${{gitea.workspace}}/build + run: ctest \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b3127da1..212281f68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -333,7 +333,8 @@ if (SLS_USE_INTEGRATION_TESTS) endif (SLS_USE_INTEGRATION_TESTS) if (SLS_USE_PYTHON) - find_package (Python 3.8 COMPONENTS Interpreter Development) + find_package (Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED) + set(PYBIND11_FINDPYTHON ON) # Needed for RH8 if(SLS_FETCH_PYBIND11_FROM_GITHUB) FetchContent_Declare( pybind11 From 27530fca31ccae8193cab4c4e77a62cb8181409d Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Tue, 29 Apr 2025 11:14:52 +0200 Subject: [PATCH 052/103] version now supports . before postfix --- update_version.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/update_version.py b/update_version.py index 2b5609198..6b3e1ebad 100644 --- a/update_version.py +++ b/update_version.py @@ -19,10 +19,11 @@ def get_version(): return "0.0.0" version = sys.argv[1] - + try: - v = Version(version) # normalize according to PEP 440 specification - return v + v = Version(version) # normalizcheck if version follows PEP 440 specification + #replace - + return version.replace("-", ".") except InvalidVersion as e: print(f"Invalid version {version}. Version format must follow semantic versioning format of python PEP 440 version identification specification.") sys.exit(1) @@ -31,7 +32,7 @@ def get_version(): def write_version_to_file(version): version_file_path = os.path.join(SCRIPT_DIR, "VERSION") with open(version_file_path, "w") as version_file: - version_file.write(str(version)) + version_file.write(version) print(f"Version {version} written to VERSION file.") # Main script From adf0124ea34cd297b874f906fb72d1c7b4674313 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Tue, 29 Apr 2025 17:35:55 +0200 Subject: [PATCH 053/103] rough draft of test acquire of all detectors for frames caught and file size. ctb not included yet --- .../tests/Caller/test-Caller.cpp | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller.cpp b/slsDetectorSoftware/tests/Caller/test-Caller.cpp index ae21ed945..469b042ab 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller.cpp @@ -12,6 +12,7 @@ #include #include "tests/globals.h" +#include namespace sls { @@ -3608,4 +3609,172 @@ TEST_CASE("sleep", "[.cmdcall]") { REQUIRE_THROWS(caller.call("sleep", {}, -1, GET)); } +TEST_CASE("acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + // save previous file state + auto file_write = + det.getFileWrite().tsquash("Inconsistent file write state"); + auto file_overwrite = + det.getFileOverWrite().tsquash("Inconsistent file overwrite state"); + auto file_format = det.getFileFormat().tsquash("Inconsistent file format"); + auto file_path = det.getFilePath().tsquash("Inconsistent file path"); + auto file_prefix = + det.getFileNamePrefix().tsquash("Inconsistent file prefix"); + auto file_acq_index = det.getAcquisitionIndex().tsquash( + "Inconsistent file acquisition index"); + // save previous det config state + auto timing_mode = det.getTimingMode().tsquash("Inconsistent timing mode"); + auto num_frames = + det.getNumberOfFrames().tsquash("Inconsistent number of frames"); + auto num_triggers = + det.getNumberOfTriggers().tsquash("Inconsistent number of triggers"); + auto period = det.getPeriod().tsquash("Inconsistent period"); + std::array exptime{}; + if (det_type != defs::MYTHEN3) { + exptime[0] = det.getExptime().tsquash("inconsistent exptime to test"); + } else { + exptime = + det.getExptimeForAllGates().tsquash("inconsistent exptime to test"); + } + // save previous specific det type config + auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( + "inconsistent number of udp interfaces"); + auto n_rows = 0; + if (det_type == defs::EIGER || det_type == defs::JUNGFRAU || + det_type == defs::MOENCH) { + n_rows = + det.getReadNRows().tsquash("inconsistent number of rows to test"); + } + auto dynamic_range = + det.getDynamicRange().tsquash("inconsistent dynamic range to test"); + + uint32_t counter_mask = 0; + if (det_type == defs::MYTHEN3) { + counter_mask = + det.getCounterMask().tsquash("inconsistent counter mask to test"); + } + int num_counters = __builtin_popcount(counter_mask); + + // defaults + int num_frames_to_acquire = 2; + int bytes_per_pixel = 2; + + // set default file state + det.setFileWrite(true); + det.setFileOverWrite(true); + det.setFileFormat(defs::BINARY); + det.setFilePath("/tmp"); + det.setFileNamePrefix("sls_test"); + det.setAcquisitionIndex(0); + // set default det config state + det.setTimingMode(defs::AUTO_TIMING); + det.setNumberOfFrames(num_frames_to_acquire); + det.setNumberOfTriggers(1); + det.setPeriod(std::chrono::milliseconds{2}); + det.setExptime(-1, std::chrono::microseconds{200}); + // set default specific det type config + if (det_type == defs::EIGER) { + det.setReadNRows(256); + } else if (det_type == defs::JUNGFRAU) { + det.setReadNRows(512); + } else if (det_type == defs::MOENCH) { + det.setReadNRows(400); + } + if (det_type == defs::EIGER || det_type == defs::MYTHEN3) { + det.setDynamicRange(bytes_per_pixel * 8); + } + if (det_type == defs::MYTHEN3) { + det.setCounterMask(0x1); + } + + // acquire + REQUIRE_NOTHROW(caller.call("rx_start", {}, -1, PUT)); + REQUIRE_NOTHROW(caller.call("start", {}, -1, PUT)); + std::this_thread::sleep_for(std::chrono::seconds{1}); + REQUIRE_NOTHROW(caller.call("rx_stop", {}, -1, PUT)); + + // check frames caught + auto frames_caught = det.getFramesCaught().tsquash( + "Inconsistent number of frames caught")[0]; + REQUIRE(frames_caught == num_frames_to_acquire); + + // check file size (assuming local pc) + size_t expected_image_size = 0; + switch (det_type) { + case defs::EIGER: + // pixels_row_per_chip * pixels_col_per_chip * num_chips * + // bytes_per_pixel + expected_image_size = 256 * 256 * 2 * bytes_per_pixel; + break; + case defs::JUNGFRAU: + // pixels_row_per_chip * pixels_col_per_chip * num_chips * + // bytes_per_pixel + if (num_udp_interfaces == 1) { + expected_image_size = 256 * 256 * 8 * bytes_per_pixel; + } else { + expected_image_size = 256 * 256 * 4 * bytes_per_pixel; + } + break; + case defs::MOENCH: + // pixels_row * pixels_col * bytes_per_pixel + if (num_udp_interfaces == 1) { + expected_image_size = 400 * 400 * bytes_per_pixel; + } else { + expected_image_size = 200 * 400 * bytes_per_pixel; + } + break; + case defs::GOTTHARD2: + // num_channels * num_chips + expected_image_size = 128 * 10 * bytes_per_pixel; + break; + case defs::MYTHEN3: + // num_channels * num_chips * num_counters + expected_image_size = 128 * 10 * num_counters * bytes_per_pixel; + break; + // to include ctb and xilinx_ctb + default: + break; + } + size_t expected_file_size = + num_frames_to_acquire * + (expected_image_size + sizeof(defs::sls_receiver_header)); + std::string fname = file_path + "/" + file_prefix + "f0_" + + std::to_string(file_acq_index) + ".raw"; + auto actual_file_size = std::filesystem::file_size(fname); + REQUIRE(actual_file_size == expected_file_size); + + // restore to previous file state + det.setFileWrite(file_write); + det.setFileOverWrite(file_overwrite); + det.setFileFormat(file_format); + det.setFilePath(file_path); + det.setFileNamePrefix(file_prefix); + det.setAcquisitionIndex(file_acq_index); + // restore to previous det config state + det.setTimingMode(timing_mode); + det.setNumberOfFrames(num_frames); + det.setNumberOfTriggers(num_triggers); + det.setPeriod(period); + if (det_type != defs::MYTHEN3) { + det.setExptime(exptime[0]); + } else { + for (int iGate = 0; iGate < 3; ++iGate) { + det.setExptime(iGate, exptime[iGate]); + } + } + // restore previous specific det type config + if (det_type == defs::JUNGFRAU || det_type == defs::MOENCH) { + det.setNumberofUDPInterfaces(num_udp_interfaces); + } + if (det_type == defs::EIGER || det_type == defs::MYTHEN3) { + det.setDynamicRange(dynamic_range); + } + if (det_type == defs::MYTHEN3) { + det.setCounterMask(counter_mask); + } + } // namespace sls \ No newline at end of file From 98b1e287a4446e7dbec9920f6fbaea6b7a4667b5 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 30 Apr 2025 09:08:07 +0200 Subject: [PATCH 054/103] moved dbitoffset, dbitreorder and dbitlist to GeneralData --- slsReceiverSoftware/src/DataProcessor.cpp | 28 ++++++------- slsReceiverSoftware/src/DataProcessor.h | 8 +--- slsReceiverSoftware/src/GeneralData.h | 29 +++++++++++++ slsReceiverSoftware/src/Implementation.cpp | 37 +++++++--------- slsReceiverSoftware/src/Implementation.h | 3 -- .../tests/test-ArrangeDataBasedOnBitList.cpp | 42 +++++++++++-------- 6 files changed, 83 insertions(+), 64 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 1956d3578..710875496 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -72,14 +72,6 @@ void DataProcessor::SetStreamingStartFnum(uint32_t value) { void DataProcessor::SetFramePadding(bool enable) { framePadding = enable; } -void DataProcessor::SetCtbDbitList(std::vector value) { - ctbDbitList = value; -} - -void DataProcessor::SetCtbDbitOffset(int value) { ctbDbitOffset = value; } - -void DataProcessor::SetCtbDbitReorder(bool value) { ctbDbitReorder = value; } - void DataProcessor::SetQuadEnable(bool value) { quadEnable = value; } void DataProcessor::SetFlipRows(bool fd) { @@ -361,13 +353,14 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, PadMissingPackets(header, data); // rearrange ctb digital bits - if (!ctbDbitList.empty()) { + if (!generalData->ctbDbitList.empty()) { ArrangeDbitData(size, data); - } else if (ctbDbitReorder) { - ctbDbitList.resize(64); + } else if (generalData->ctbDbitReorder) { + std::vector ctbDbitList(64); std::iota(ctbDbitList.begin(), ctbDbitList.end(), 0); + generalData->SetctbDbitList(ctbDbitList); ArrangeDbitData(size, data); - } else if (ctbDbitOffset > 0) { + } else if (generalData->ctbDbitOffset > 0) { RemoveTrailingBits(size, data); } @@ -542,6 +535,7 @@ void DataProcessor::RemoveTrailingBits(size_t &size, char *data) { const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); const size_t nTransceiverDataBytes = generalData->GetNumberOfTransceiverDatabytes(); + const size_t ctbDbitOffset = generalData->ctbDbitOffset; const size_t ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; @@ -568,10 +562,14 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { std::to_string(generalData->detType)); } - size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); - size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); - size_t nTransceiverDataBytes = + const size_t nAnalogDataBytes = generalData->GetNumberOfAnalogDatabytes(); + const size_t nDigitalDataBytes = generalData->GetNumberOfDigitalDatabytes(); + const size_t nTransceiverDataBytes = generalData->GetNumberOfTransceiverDatabytes(); + const size_t ctbDbitOffset = generalData->ctbDbitOffset; + const bool ctbDbitReorder = generalData->ctbDbitReorder; + const auto ctbDbitList = generalData->ctbDbitList; + // TODO! (Erik) Refactor and add tests int ctbDigitalDataBytes = nDigitalDataBytes - ctbDbitOffset; diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index 16778ec23..e21d73c90 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -45,9 +45,6 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { void SetStreamingTimerInMs(uint32_t value); void SetStreamingStartFnum(uint32_t value); void SetFramePadding(bool enable); - void SetCtbDbitList(std::vector value); - void SetCtbDbitOffset(int value); - void SetCtbDbitReorder(bool value); void SetQuadEnable(bool value); void SetFlipRows(bool fd); void SetNumberofTotalFrames(uint64_t value); @@ -171,11 +168,8 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin {}; + struct timespec timerbegin{}; bool framePadding; - std::vector ctbDbitList{}; - int ctbDbitOffset{0}; - bool ctbDbitReorder{true}; std::atomic startedFlag{false}; std::atomic firstIndex{0}; bool quadEnable{false}; diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 17a4ea7e5..6657630cc 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -52,6 +52,9 @@ class GeneralData { uint32_t nAnalogSamples{0}; uint32_t nDigitalSamples{0}; uint32_t nTransceiverSamples{0}; + std::vector ctbDbitList; + int ctbDbitOffset{0}; + bool ctbDbitReorder{false}; slsDetectorDefs::readoutMode readoutType{slsDetectorDefs::ANALOG_ONLY}; uint32_t adcEnableMaskOneGiga{BIT32_MASK}; uint32_t adcEnableMaskTenGiga{BIT32_MASK}; @@ -148,6 +151,18 @@ class GeneralData { virtual void SetTransceiverEnableMask(int n) { ThrowGenericError("SetTransceiverEnableMask"); }; + + virtual void SetctbDbitOffset(const int n) { + ThrowGenericError("SetctbDbitOffset"); + }; + + virtual void SetctbDbitList(const std::vector &value) { + ThrowGenericError("SetctbDbitList"); + }; + + virtual void SetctbDbitReorder(const bool reorder) { + ThrowGenericError("SetctbDbitReorder"); + }; }; class EigerData : public GeneralData { @@ -387,6 +402,7 @@ class ChipTestBoardData : public GeneralData { framesPerFile = CTB_MAX_FRAMES_PER_FILE; fifoDepth = 2500; standardheader = true; + ctbDbitReorder = true; UpdateImageSize(); }; @@ -412,6 +428,12 @@ class ChipTestBoardData : public GeneralData { UpdateImageSize(); }; + void SetctbDbitOffset(const int value) { ctbDbitOffset = value; } + + void SetctbDbitList(const std::vector &value) { ctbDbitList = value; } + + void SetctbDbitReorder(const bool value) { ctbDbitReorder = value; } + void SetOneGigaAdcEnableMask(int n) { adcEnableMaskOneGiga = n; UpdateImageSize(); @@ -518,6 +540,7 @@ class XilinxChipTestBoardData : public GeneralData { dataSize = 8144; packetSize = headerSizeinPacket + dataSize; tengigaEnable = true; + ctbDbitReorder = true; UpdateImageSize(); }; @@ -543,6 +566,12 @@ class XilinxChipTestBoardData : public GeneralData { UpdateImageSize(); }; + void SetctbDbitOffset(const int value) { ctbDbitOffset = value; } + + void SetctbDbitList(const std::vector &value) { ctbDbitList = value; } + + void SetctbDbitReorder(const bool value) { ctbDbitReorder = value; } + void SetOneGigaAdcEnableMask(int n) { adcEnableMaskOneGiga = n; UpdateImageSize(); diff --git a/slsReceiverSoftware/src/Implementation.cpp b/slsReceiverSoftware/src/Implementation.cpp index e4385d9c0..f9480c5d4 100644 --- a/slsReceiverSoftware/src/Implementation.cpp +++ b/slsReceiverSoftware/src/Implementation.cpp @@ -200,9 +200,6 @@ void Implementation::SetupDataProcessor(int i) { dataProcessor[i]->SetStreamingTimerInMs(streamingTimerInMs); dataProcessor[i]->SetStreamingStartFnum(streamingStartFnum); dataProcessor[i]->SetFramePadding(framePadding); - dataProcessor[i]->SetCtbDbitList(ctbDbitList); - dataProcessor[i]->SetCtbDbitOffset(ctbDbitOffset); - dataProcessor[i]->SetCtbDbitReorder(ctbDbitReorder); dataProcessor[i]->SetQuadEnable(quadEnable); dataProcessor[i]->SetFlipRows(flipRows); dataProcessor[i]->SetNumberofTotalFrames(numberOfTotalFrames); @@ -991,11 +988,11 @@ void Implementation::StartMasterWriter() { ? 1 : 0; masterAttributes.digitalSamples = generalData->nDigitalSamples; - masterAttributes.dbitoffset = ctbDbitOffset; - masterAttributes.dbitreorder = ctbDbitReorder; + masterAttributes.dbitoffset = generalData->ctbDbitOffset; + masterAttributes.dbitreorder = generalData->ctbDbitReorder; masterAttributes.dbitlist = 0; - for (auto &i : ctbDbitList) { + for (auto &i : generalData->ctbDbitList) { masterAttributes.dbitlist |= (static_cast(1) << i); } masterAttributes.transceiverSamples = @@ -1751,31 +1748,29 @@ void Implementation::setTenGigaADCEnableMask(uint32_t mask) { LOG(logINFO) << "Packets per Frame: " << (generalData->packetsPerFrame); } -std::vector Implementation::getDbitList() const { return ctbDbitList; } +std::vector Implementation::getDbitList() const { + return generalData->ctbDbitList; +} void Implementation::setDbitList(const std::vector &v) { - ctbDbitList = v; - for (const auto &it : dataProcessor) - it->SetCtbDbitList(ctbDbitList); - LOG(logINFO) << "Dbit list: " << ToString(ctbDbitList); + generalData->SetctbDbitList(v); + LOG(logINFO) << "Dbit list: " << ToString(v); } -int Implementation::getDbitOffset() const { return ctbDbitOffset; } +int Implementation::getDbitOffset() const { return generalData->ctbDbitOffset; } void Implementation::setDbitOffset(const int s) { - ctbDbitOffset = s; - for (const auto &it : dataProcessor) - it->SetCtbDbitOffset(ctbDbitOffset); - LOG(logINFO) << "Dbit offset: " << ctbDbitOffset; + generalData->SetctbDbitOffset(s); + LOG(logINFO) << "Dbit offset: " << s; } -bool Implementation::getDbitReorder() const { return ctbDbitReorder; } +bool Implementation::getDbitReorder() const { + return generalData->ctbDbitReorder; +} void Implementation::setDbitReorder(const bool reorder) { - ctbDbitReorder = reorder; - for (const auto &it : dataProcessor) - it->SetCtbDbitReorder(ctbDbitReorder); - LOG(logINFO) << "Dbit reorder: " << ctbDbitReorder; + generalData->SetctbDbitReorder(reorder); + LOG(logINFO) << "Dbit reorder: " << reorder; } uint32_t Implementation::getTransceiverEnableMask() const { diff --git a/slsReceiverSoftware/src/Implementation.h b/slsReceiverSoftware/src/Implementation.h index 79baa670b..9fbf1fd28 100644 --- a/slsReceiverSoftware/src/Implementation.h +++ b/slsReceiverSoftware/src/Implementation.h @@ -370,9 +370,6 @@ class Implementation : private virtual slsDetectorDefs { int thresholdEnergyeV{-1}; std::array thresholdAllEnergyeV = {{-1, -1, -1}}; std::vector rateCorrections; - std::vector ctbDbitList; - int ctbDbitOffset{0}; - bool ctbDbitReorder{true}; // callbacks void (*startAcquisitionCallBack)(const startCallbackHeader, diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index f65ff3e80..df87f392f 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -32,17 +32,23 @@ class GeneralDataTest : public GeneralData { nTransceiverBytes = value; } + void SetCtbDbitOffset(const int value) { ctbDbitOffset = value; } + + void SetCtbDbitList(const std::vector &value) { ctbDbitList = value; } + + void SetCtbDbitReorder(const bool value) { ctbDbitReorder = value; } + private: - int nAnalogBytes; - int nDigitalBytes; - int nTransceiverBytes; + int nAnalogBytes{}; + int nDigitalBytes{}; + int nTransceiverBytes{}; }; // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0){}; - ~DataProcessorTest(){}; + DataProcessorTest() : DataProcessor(0) {}; + ~DataProcessorTest() {}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } @@ -140,7 +146,7 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Remove Trailing Bits", set_random_offset_bytes(num_random_offset_bytes); set_data(); - dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); + generaldata->SetCtbDbitOffset(num_random_offset_bytes); size_t expected_size = get_size() - num_random_offset_bytes; @@ -183,8 +189,8 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Reorder all", std::vector bitlist(64); std::iota(bitlist.begin(), bitlist.end(), 0); - dataprocessor->SetCtbDbitList(bitlist); - dataprocessor->SetCtbDbitReorder(true); // set reorder to true + generaldata->SetCtbDbitList(bitlist); + generaldata->SetCtbDbitReorder(true); // set reorder to true const size_t expected_size = num_analog_bytes + num_transceiver_bytes + expected_num_digital_bytes; @@ -222,9 +228,9 @@ TEST_CASE_METHOD(DataProcessorTestFixture, std::vector bitlist(64); std::iota(bitlist.begin(), bitlist.end(), 0); - dataprocessor->SetCtbDbitList(bitlist); - dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); - dataprocessor->SetCtbDbitReorder(true); // set reorder to true + generaldata->SetCtbDbitList(bitlist); + generaldata->SetCtbDbitOffset(num_random_offset_bytes); + generaldata->SetCtbDbitReorder(true); // set reorder to true const size_t expected_num_digital_bytes = 64; std::vector expected_digital_part{0b00011111}; @@ -272,9 +278,9 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder false", std::tie(num_samples, bitlist, expected_num_digital_bytes, expected_digital_part) = parameters; - dataprocessor->SetCtbDbitList(bitlist); + generaldata->SetCtbDbitList(bitlist); - dataprocessor->SetCtbDbitReorder(false); + generaldata->SetCtbDbitReorder(false); set_num_samples(num_samples); set_data(); @@ -324,9 +330,9 @@ TEST_CASE_METHOD(DataProcessorTestFixture, "Arrange bitlist with reorder true", std::tie(num_samples, bitlist, expected_num_digital_bytes, expected_digital_part) = parameters; - dataprocessor->SetCtbDbitList(bitlist); + generaldata->SetCtbDbitList(bitlist); - dataprocessor->SetCtbDbitReorder(true); + generaldata->SetCtbDbitReorder(true); set_num_samples(num_samples); set_data(); @@ -368,11 +374,11 @@ TEST_CASE_METHOD(DataProcessorTestFixture, set_random_offset_bytes(num_random_offset_bytes); set_data(); - dataprocessor->SetCtbDbitList(bitlist); + generaldata->SetCtbDbitList(bitlist); - dataprocessor->SetCtbDbitReorder(false); + generaldata->SetCtbDbitReorder(false); - dataprocessor->SetCtbDbitOffset(num_random_offset_bytes); + generaldata->SetCtbDbitOffset(num_random_offset_bytes); std::vector expected_digital_part{0b00000111}; const size_t expected_num_digital_bytes = 5; From 062002243ef1bec664951fb86b449aeecbeef2d4 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Wed, 30 Apr 2025 10:54:34 +0200 Subject: [PATCH 055/103] added error message on receiver side, throw error --- slsReceiverSoftware/src/DataProcessor.cpp | 4 +++- slsReceiverSoftware/src/GeneralData.h | 2 +- slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 710875496..cfad27a3d 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -295,7 +295,9 @@ void DataProcessor::ThreadExecution() { memImage->data); } catch (const std::exception &e) { fifo->FreeAddress(buffer); - return; + LOG(logERROR) << "DataProcessor " << index << ": Failed to Process. " + << e.what() << std::endl; + throw RuntimeError(e.what()); } // stream (if time/freq to stream) or free diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 6657630cc..7147d0e2c 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -52,7 +52,7 @@ class GeneralData { uint32_t nAnalogSamples{0}; uint32_t nDigitalSamples{0}; uint32_t nTransceiverSamples{0}; - std::vector ctbDbitList; + std::vector ctbDbitList{}; int ctbDbitOffset{0}; bool ctbDbitReorder{false}; slsDetectorDefs::readoutMode readoutType{slsDetectorDefs::ANALOG_ONLY}; diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index df87f392f..7ad93b908 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -73,8 +73,6 @@ class DataProcessorTestFixture { dataprocessor = new DataProcessorTest; generaldata = new GeneralDataTest; - // set_num_samples(num_samples); - generaldata->SetNumberOfAnalogDatabytes(num_analog_bytes); generaldata->SetNumberOfTransceiverDatabytes(num_transceiver_bytes); generaldata->SetNumberOfDigitalDatabytes(num_digital_bytes + From 36faec6ad34ca4710fedd885c0bb11130e6a7090 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 30 Apr 2025 12:17:16 +0200 Subject: [PATCH 056/103] removed log as error already printed --- slsReceiverSoftware/src/DataProcessor.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index cfad27a3d..50a41753e 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -295,8 +295,6 @@ void DataProcessor::ThreadExecution() { memImage->data); } catch (const std::exception &e) { fifo->FreeAddress(buffer); - LOG(logERROR) << "DataProcessor " << index << ": Failed to Process. " - << e.what() << std::endl; throw RuntimeError(e.what()); } From f09879a46c06435f637c3b9d2013bd42fec1f826 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 30 Apr 2025 15:00:00 +0200 Subject: [PATCH 057/103] added tests to check file size and frames caught with an acquire (virtual) for every detector --- .../Caller/test-Caller-chiptestboard.cpp | 251 ++++++++++++++++++ .../tests/Caller/test-Caller-eiger.cpp | 56 ++++ .../tests/Caller/test-Caller-global.cpp | 87 ++++++ .../tests/Caller/test-Caller-global.h | 40 +++ .../tests/Caller/test-Caller-gotthard2.cpp | 57 ++++ .../tests/Caller/test-Caller-jungfrau.cpp | 57 ++++ .../tests/Caller/test-Caller-moench.cpp | 56 ++++ .../tests/Caller/test-Caller-mythen3.cpp | 55 ++++ .../tests/Caller/test-Caller.cpp | 168 ------------ slsReceiverSoftware/src/DataProcessor.cpp | 13 +- 10 files changed, 667 insertions(+), 173 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index fe30c9d2d..2bba9b474 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -17,6 +17,257 @@ namespace sls { using test::GET; using test::PUT; +struct testCtbAcquireInfo { + defs::readoutMode readout_mode{defs::ANALOG_AND_DIGITAL}; + bool teng_giga{false}; + int num_adc_samples{5000}; + int num_dbit_samples{6000}; + int num_trans_samples{288}; + uint32_t adc_enable_1g{0xFFFFFFFF}; + uint32_t adc_enable_10g{0xFFFFFFFF}; + int dbit_offset{0}; + std::vector dbit_list{0, 12, 2, 43}; + bool dbit_reorder{false}; + uint32_t transceiver_mask{0x3}; +}; + +testCtbAcquireInfo get_ctb_config_state(const Detector &det) { + return testCtbAcquireInfo{ + det.getReadoutMode().tsquash("inconsistent readout mode to test"), + det.getTenGiga().tsquash("inconsistent ten giga enable to test"), + det.getNumberOfAnalogSamples().tsquash( + "inconsistent number of analog samples to test"), + det.getNumberOfDigitalSamples().tsquash( + "inconsistent number of digital samples to test"), + det.getNumberOfTransceiverSamples().tsquash( + "inconsistent number of transceiver samples to test"), + det.getADCEnableMask().tsquash("inconsistent adc enable mask to test"), + det.getTenGigaADCEnableMask().tsquash( + "inconsistent ten giga adc enable mask to test"), + det.getRxDbitOffset().tsquash("inconsistent rx dbit offset to test"), + det.getRxDbitList().tsquash("inconsistent rx dbit list to test"), + det.getRxDbitReorder().tsquash("inconsistent rx dbit reorder to test"), + det.getTransceiverEnableMask().tsquash( + "inconsistent transceiver mask to test")}; +} + +void set_ctb_config_state(Detector &det, + const testCtbAcquireInfo &ctb_config_info) { + det.setReadoutMode(ctb_config_info.readout_mode); + det.setTenGiga(ctb_config_info.teng_giga); + det.setNumberOfAnalogSamples(ctb_config_info.num_adc_samples); + det.setNumberOfDigitalSamples(ctb_config_info.num_dbit_samples); + det.setNumberOfTransceiverSamples(ctb_config_info.num_trans_samples); + det.setADCEnableMask(ctb_config_info.adc_enable_1g); + det.setTenGigaADCEnableMask(ctb_config_info.adc_enable_10g); + det.setRxDbitOffset(ctb_config_info.dbit_offset); + det.setRxDbitList(ctb_config_info.dbit_list); + det.setRxDbitReorder(ctb_config_info.dbit_reorder); + det.setTransceiverEnableMask(ctb_config_info.transceiver_mask); +} + +void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, + int64_t num_frames_to_acquire, + Detector &det, Caller &caller) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + // overwrite exptime if not using virtual ctb server + get_common_acquire_config_state(det); + testCtbAcquireInfo prev_ctb_config_info = get_ctb_config_state(det); + + // defaults + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set ctb config + set_ctb_config_state(det, test_info); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // calculate image size + uint64_t num_analog_bytes = 0, num_digital_bytes = 0, + num_transceiver_bytes = 0; + if (test_info.readout_mode == defs::ANALOG_ONLY || + test_info.readout_mode == defs::ANALOG_AND_DIGITAL) { + uint32_t adc_enable_mask = + (test_info.teng_giga ? test_info.adc_enable_1g + : test_info.adc_enable_10g); + int num_analog_chans = __builtin_popcount(adc_enable_mask); + const int num_bytes_per_sample = 2; + num_analog_bytes = + num_analog_chans * num_bytes_per_sample * test_info.num_adc_samples; + std::cout << "[Analog Databytes: " << num_analog_bytes << ']'; + } + + // digital channels + if (test_info.readout_mode == defs::DIGITAL_ONLY || + test_info.readout_mode == defs::ANALOG_AND_DIGITAL || + test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { + int num_digital_samples = test_info.num_dbit_samples; + if (test_info.dbit_offset > 0) { + uint64_t num_digital_bytes_reserved = + num_digital_samples * sizeof(uint64_t); + num_digital_bytes_reserved -= test_info.dbit_offset; + num_digital_samples = num_digital_bytes_reserved / sizeof(uint64_t); + } + int num_digital_chans = test_info.dbit_list.size(); + if (num_digital_chans == 0) { + num_digital_chans = 64; + } + if (!test_info.dbit_reorder) { + uint32_t num_bits_per_sample = num_digital_chans; + if (num_bits_per_sample % 8 != 0) { + num_bits_per_sample += (8 - (num_bits_per_sample % 8)); + } + num_digital_bytes = (num_bits_per_sample / 8) * num_digital_samples; + } else { + uint32_t num_bits_per_bit = num_digital_samples; + if (num_bits_per_bit % 8 != 0) { + num_bits_per_bit += (8 - (num_bits_per_bit % 8)); + } + num_digital_bytes = num_digital_chans * (num_bits_per_bit / 8); + } + std::cout << "[Digital Databytes: " << num_digital_bytes << ']'; + } + // transceiver channels + if (test_info.readout_mode == defs::TRANSCEIVER_ONLY || + test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { + int num_transceiver_chans = + __builtin_popcount(test_info.transceiver_mask); + const int num_bytes_per_channel = 8; + num_transceiver_bytes = num_transceiver_chans * num_bytes_per_channel * + test_info.num_trans_samples; + std::cout << "[Transceiver Databytes: " << num_transceiver_bytes << ']'; + } + std::cout << std::endl; + // check file size (assuming local pc) + uint64_t expected_image_size = + num_analog_bytes + num_digital_bytes + num_transceiver_bytes; + std::cout << "Expected image size: " << expected_image_size << std::endl; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + set_ctb_config_state(det, prev_ctb_config_info); +} + +TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::CHIPTESTBOARD || + det_type == defs::XILINX_CHIPTESTBOARD) { + int num_frames_to_acquire = 2; + // all the test cases + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_list.clear(); + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + } +} + /* dacs */ TEST_CASE("dacname", "[.cmdcall]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp index ae6a6c824..b59083add 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp @@ -17,6 +17,62 @@ namespace sls { using test::GET; using test::PUT; +TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::EIGER) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + get_common_acquire_config_state(det); + + // save previous specific det type config + auto n_rows = + det.getReadNRows().tsquash("inconsistent number of rows to test"); + auto dynamic_range = + det.getDynamicRange().tsquash("inconsistent dynamic range to test"); + REQUIRE(false == + det.getTenGiga().tsquash("inconsistent 10Giga to test")); + + // defaults + int num_frames_to_acquire = 2; + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set default specific det type config + det.setReadNRows(256); + det.setDynamicRange(16); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + // pixels_row_per_chip * pixels_col_per_chip * num_chips * + // bytes_per_pixel + size_t expected_image_size = 256 * 256 * 2 * 2; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + + // restore previous specific det type config + det.setReadNRows(n_rows); + det.setDynamicRange(dynamic_range); + } +} + /** temperature */ TEST_CASE("temp_fpgaext", "[.cmdcall]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp index 7900f3340..84e65656e 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp @@ -88,4 +88,91 @@ void test_onchip_dac_caller(defs::dacIndex index, const std::string &dacname, } } +testFileInfo get_file_state(const Detector &det) { + return testFileInfo{ + det.getFilePath().tsquash("Inconsistent file path"), + det.getFileNamePrefix().tsquash("Inconsistent file prefix"), + det.getAcquisitionIndex().tsquash( + "Inconsistent file acquisition index"), + det.getFileWrite().tsquash("Inconsistent file write state"), + det.getFileOverWrite().tsquash("Inconsistent file overwrite state"), + det.getFileFormat().tsquash("Inconsistent file format")}; +} + +void set_file_state(Detector &det, const testFileInfo &file_info) { + if (!file_info.file_path.empty()) + det.setFilePath(file_info.file_path); + det.setFileNamePrefix(file_info.file_prefix); + det.setAcquisitionIndex(file_info.file_acq_index); + det.setFileWrite(file_info.file_write); + det.setFileOverWrite(file_info.file_overwrite); + det.setFileFormat(file_info.file_format); +} + +void test_acquire_binary_file_size(const testFileInfo &file_info, + uint64_t num_frames_to_acquire, + uint64_t expected_image_size) { + assert(file_info.file_format == defs::BINARY); + std::string fname = file_info.file_path + "/" + file_info.file_prefix + + "_d0_f0_" + std::to_string(file_info.file_acq_index) + + ".raw"; + uint64_t expected_file_size = + num_frames_to_acquire * + (expected_image_size + sizeof(defs::sls_receiver_header)); + std::cout << "exepected file size: " << expected_file_size + << " receiver header size :" << sizeof(defs::sls_receiver_header) + << " num frames:" << num_frames_to_acquire << std::endl; + auto actual_file_size = std::filesystem::file_size(fname); + REQUIRE(actual_file_size == expected_file_size); +} + +void test_frames_caught(const Detector &det, int num_frames_to_acquire) { + auto frames_caught = det.getFramesCaught().tsquash( + "Inconsistent number of frames caught")[0]; + REQUIRE(frames_caught == num_frames_to_acquire); +} + +void test_acquire_with_receiver(Caller &caller, std::chrono::seconds timeout) { + REQUIRE_NOTHROW(caller.call("rx_start", {}, -1, PUT)); + REQUIRE_NOTHROW(caller.call("start", {}, -1, PUT)); + std::this_thread::sleep_for(timeout); + REQUIRE_NOTHROW(caller.call("rx_stop", {}, -1, PUT)); +} + +testCommonDetAcquireInfo get_common_acquire_config_state(const Detector &det) { + testCommonDetAcquireInfo det_config_info{ + det.getTimingMode().tsquash("Inconsistent timing mode"), + det.getNumberOfFrames().tsquash("Inconsistent number of frames"), + det.getNumberOfTriggers().tsquash("Inconsistent number of triggers"), + det.getPeriod().tsquash("Inconsistent period"), + {}}; + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + if (det_type != defs::MYTHEN3) { + det_config_info.exptime[0] = + det.getExptime().tsquash("inconsistent exptime to test"); + } else { + det_config_info.exptime = + det.getExptimeForAllGates().tsquash("inconsistent exptime to test"); + } + return det_config_info; +} + +void set_common_acquire_config_state( + Detector &det, const testCommonDetAcquireInfo &det_config_info) { + det.setTimingMode(det_config_info.timing_mode); + det.setNumberOfFrames(det_config_info.num_frames_to_acquire); + det.setNumberOfTriggers(det_config_info.num_triggers); + det.setPeriod(det_config_info.period); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + if (det_type != defs::MYTHEN3) { + det.setExptime(det_config_info.exptime[0]); + } else { + for (int iGate = 0; iGate < 3; ++iGate) { + det.setExptime(iGate, det_config_info.exptime[iGate]); + } + } +} + } // namespace sls diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.h b/slsDetectorSoftware/tests/Caller/test-Caller-global.h index 98fa46860..d7f777deb 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.h +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.h @@ -1,9 +1,35 @@ // SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package #pragma once + +class Caller; +#include "sls/Detector.h" #include "sls/sls_detector_defs.h" +#include +#include +#include + namespace sls { +struct testFileInfo { + std::string file_path{"/tmp"}; + std::string file_prefix{"sls_test"}; + int64_t file_acq_index{0}; + bool file_write{true}; + bool file_overwrite{true}; + slsDetectorDefs::fileFormat file_format{slsDetectorDefs::BINARY}; +}; + +struct testCommonDetAcquireInfo { + slsDetectorDefs::timingMode timing_mode{slsDetectorDefs::AUTO_TIMING}; + int64_t num_frames_to_acquire{2}; + int64_t num_triggers{1}; + std::chrono::nanoseconds period{std::chrono::milliseconds{2}}; + std::array exptime{ + std::chrono::microseconds{200}, std::chrono::nanoseconds{0}, + std::chrono::nanoseconds{0}}; +}; + void test_valid_port_caller(const std::string &command, const std::vector &arguments, int detector_id, int action); @@ -13,4 +39,18 @@ void test_dac_caller(slsDetectorDefs::dacIndex index, void test_onchip_dac_caller(slsDetectorDefs::dacIndex index, const std::string &dacname, int dacvalue); +testFileInfo get_file_state(const Detector &det); +void set_file_state(Detector &det, const testFileInfo &file_info); +void test_acquire_binary_file_size(const testFileInfo &file_info, + uint64_t num_frames_to_acquire, + uint64_t expected_image_size); + +void test_frames_caught(const Detector &det, int num_frames_to_acquire); + +void test_acquire_with_receiver(Caller &caller, std::chrono::seconds timeout); + +testCommonDetAcquireInfo get_common_acquire_config_state(const Detector &det); +void set_common_acquire_config_state( + Detector &det, const testCommonDetAcquireInfo &det_config_info); + } // namespace sls diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp index 54bba5ce2..a747c46dd 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp @@ -17,6 +17,63 @@ namespace sls { using test::GET; using test::PUT; +TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::GOTTHARD2) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + get_common_acquire_config_state(det); + + // save previous specific det type config + auto burst_mode = + det.getBurstMode().tsquash("inconsistent burst mode to test"); + auto number_of_bursts = det.getNumberOfBursts().tsquash( + "inconsistent number of bursts to test"); + auto burst_period = + det.getBurstPeriod().tsquash("inconsistent burst period to test"); + + // defaults + int num_frames_to_acquire = 2; + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set default specific det type config + det.setBurstMode(defs::CONTINUOUS_EXTERNAL); + det.setNumberOfBursts(1); + det.setBurstPeriod(std::chrono::milliseconds{0}); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + // num_channels * num_chips * bytes_per_pixel * num_frames + size_t expected_image_size = 128 * 10 * 2; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + + // restore previous specific det type config + det.setBurstMode(burst_mode); + det.setNumberOfBursts(number_of_bursts); + det.setBurstPeriod(burst_period); + } +} + // time specific measurements for gotthard2 TEST_CASE("timegotthard2", "[.cmdcall]") { Detector det; diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp index 4563cd32f..413937632 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp @@ -15,6 +15,63 @@ namespace sls { using test::GET; using test::PUT; +TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::JUNGFRAU) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + get_common_acquire_config_state(det); + + // save previous specific det type config + auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( + "inconsistent number of udp interfaces"); + auto n_rows = + det.getReadNRows().tsquash("inconsistent number of rows to test"); + + // defaults + int num_frames_to_acquire = 2; + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set default specific det type config + det.setReadNRows(512); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + size_t expected_image_size = 0; + // pixels_row_per_chip * pixels_col_per_chip * num_chips * + // bytes_per_pixel + if (num_udp_interfaces == 1) { + expected_image_size = 256 * 256 * 8 * 2; + } else { + expected_image_size = 256 * 256 * 4 * 2; + } + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + + // restore previous specific det type config + det.setReadNRows(n_rows); + } +} + /* dacs */ TEST_CASE("Setting and reading back Jungfrau dacs", "[.cmdcall][.dacs]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp index 56353f44b..577a2f3b2 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp @@ -15,6 +15,62 @@ namespace sls { using test::GET; using test::PUT; +TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::MOENCH) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + get_common_acquire_config_state(det); + + // save previous specific det type config + auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( + "inconsistent number of udp interfaces"); + auto n_rows = + det.getReadNRows().tsquash("inconsistent number of rows to test"); + + // defaults + int num_frames_to_acquire = 2; + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set default specific det type config + det.setReadNRows(400); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + size_t expected_image_size = 0; + // pixels_row * pixels_col * bytes_per_pixel + if (num_udp_interfaces == 1) { + expected_image_size = 400 * 400 * 2; + } else { + expected_image_size = 400 * 200 * 2; + } + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + + // restore previous specific det type config + det.setReadNRows(n_rows); + } +} + /* dacs */ TEST_CASE("Setting and reading back moench dacs", "[.cmdcall][.dacs]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp index 16295a3e1..598439407 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp @@ -17,6 +17,61 @@ namespace sls { using test::GET; using test::PUT; +TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::MYTHEN3) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + get_common_acquire_config_state(det); + + // save previous specific det type config + auto dynamic_range = + det.getDynamicRange().tsquash("inconsistent dynamic range to test"); + uint32_t counter_mask = + det.getCounterMask().tsquash("inconsistent counter mask to test"); + + // defaults + int num_frames_to_acquire = 2; + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set default specific det type config + det.setDynamicRange(16); + int test_counter_mask = 0x1; + int num_counters = __builtin_popcount(counter_mask); + det.setCounterMask(test_counter_mask); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + // num_channels * num_chips * num_counters + size_t expected_image_size = 128 * 10 * num_counters * 2; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + + // restore previous specific det type config + det.setDynamicRange(dynamic_range); + det.setCounterMask(counter_mask); + } +} + /* dacs */ TEST_CASE("Setting and reading back MYTHEN3 dacs", "[.cmdcall][.dacs]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller.cpp b/slsDetectorSoftware/tests/Caller/test-Caller.cpp index 469b042ab..1ce35b41a 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller.cpp @@ -3609,172 +3609,4 @@ TEST_CASE("sleep", "[.cmdcall]") { REQUIRE_THROWS(caller.call("sleep", {}, -1, GET)); } -TEST_CASE("acquire_check_file_size", "[.cmdcall]") { - Detector det; - Caller caller(&det); - auto det_type = - det.getDetectorType().tsquash("Inconsistent detector types to test"); - - // save previous file state - auto file_write = - det.getFileWrite().tsquash("Inconsistent file write state"); - auto file_overwrite = - det.getFileOverWrite().tsquash("Inconsistent file overwrite state"); - auto file_format = det.getFileFormat().tsquash("Inconsistent file format"); - auto file_path = det.getFilePath().tsquash("Inconsistent file path"); - auto file_prefix = - det.getFileNamePrefix().tsquash("Inconsistent file prefix"); - auto file_acq_index = det.getAcquisitionIndex().tsquash( - "Inconsistent file acquisition index"); - // save previous det config state - auto timing_mode = det.getTimingMode().tsquash("Inconsistent timing mode"); - auto num_frames = - det.getNumberOfFrames().tsquash("Inconsistent number of frames"); - auto num_triggers = - det.getNumberOfTriggers().tsquash("Inconsistent number of triggers"); - auto period = det.getPeriod().tsquash("Inconsistent period"); - std::array exptime{}; - if (det_type != defs::MYTHEN3) { - exptime[0] = det.getExptime().tsquash("inconsistent exptime to test"); - } else { - exptime = - det.getExptimeForAllGates().tsquash("inconsistent exptime to test"); - } - // save previous specific det type config - auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( - "inconsistent number of udp interfaces"); - auto n_rows = 0; - if (det_type == defs::EIGER || det_type == defs::JUNGFRAU || - det_type == defs::MOENCH) { - n_rows = - det.getReadNRows().tsquash("inconsistent number of rows to test"); - } - auto dynamic_range = - det.getDynamicRange().tsquash("inconsistent dynamic range to test"); - - uint32_t counter_mask = 0; - if (det_type == defs::MYTHEN3) { - counter_mask = - det.getCounterMask().tsquash("inconsistent counter mask to test"); - } - int num_counters = __builtin_popcount(counter_mask); - - // defaults - int num_frames_to_acquire = 2; - int bytes_per_pixel = 2; - - // set default file state - det.setFileWrite(true); - det.setFileOverWrite(true); - det.setFileFormat(defs::BINARY); - det.setFilePath("/tmp"); - det.setFileNamePrefix("sls_test"); - det.setAcquisitionIndex(0); - // set default det config state - det.setTimingMode(defs::AUTO_TIMING); - det.setNumberOfFrames(num_frames_to_acquire); - det.setNumberOfTriggers(1); - det.setPeriod(std::chrono::milliseconds{2}); - det.setExptime(-1, std::chrono::microseconds{200}); - // set default specific det type config - if (det_type == defs::EIGER) { - det.setReadNRows(256); - } else if (det_type == defs::JUNGFRAU) { - det.setReadNRows(512); - } else if (det_type == defs::MOENCH) { - det.setReadNRows(400); - } - if (det_type == defs::EIGER || det_type == defs::MYTHEN3) { - det.setDynamicRange(bytes_per_pixel * 8); - } - if (det_type == defs::MYTHEN3) { - det.setCounterMask(0x1); - } - - // acquire - REQUIRE_NOTHROW(caller.call("rx_start", {}, -1, PUT)); - REQUIRE_NOTHROW(caller.call("start", {}, -1, PUT)); - std::this_thread::sleep_for(std::chrono::seconds{1}); - REQUIRE_NOTHROW(caller.call("rx_stop", {}, -1, PUT)); - - // check frames caught - auto frames_caught = det.getFramesCaught().tsquash( - "Inconsistent number of frames caught")[0]; - REQUIRE(frames_caught == num_frames_to_acquire); - - // check file size (assuming local pc) - size_t expected_image_size = 0; - switch (det_type) { - case defs::EIGER: - // pixels_row_per_chip * pixels_col_per_chip * num_chips * - // bytes_per_pixel - expected_image_size = 256 * 256 * 2 * bytes_per_pixel; - break; - case defs::JUNGFRAU: - // pixels_row_per_chip * pixels_col_per_chip * num_chips * - // bytes_per_pixel - if (num_udp_interfaces == 1) { - expected_image_size = 256 * 256 * 8 * bytes_per_pixel; - } else { - expected_image_size = 256 * 256 * 4 * bytes_per_pixel; - } - break; - case defs::MOENCH: - // pixels_row * pixels_col * bytes_per_pixel - if (num_udp_interfaces == 1) { - expected_image_size = 400 * 400 * bytes_per_pixel; - } else { - expected_image_size = 200 * 400 * bytes_per_pixel; - } - break; - case defs::GOTTHARD2: - // num_channels * num_chips - expected_image_size = 128 * 10 * bytes_per_pixel; - break; - case defs::MYTHEN3: - // num_channels * num_chips * num_counters - expected_image_size = 128 * 10 * num_counters * bytes_per_pixel; - break; - // to include ctb and xilinx_ctb - default: - break; - } - size_t expected_file_size = - num_frames_to_acquire * - (expected_image_size + sizeof(defs::sls_receiver_header)); - std::string fname = file_path + "/" + file_prefix + "f0_" + - std::to_string(file_acq_index) + ".raw"; - auto actual_file_size = std::filesystem::file_size(fname); - REQUIRE(actual_file_size == expected_file_size); - - // restore to previous file state - det.setFileWrite(file_write); - det.setFileOverWrite(file_overwrite); - det.setFileFormat(file_format); - det.setFilePath(file_path); - det.setFileNamePrefix(file_prefix); - det.setAcquisitionIndex(file_acq_index); - // restore to previous det config state - det.setTimingMode(timing_mode); - det.setNumberOfFrames(num_frames); - det.setNumberOfTriggers(num_triggers); - det.setPeriod(period); - if (det_type != defs::MYTHEN3) { - det.setExptime(exptime[0]); - } else { - for (int iGate = 0; iGate < 3; ++iGate) { - det.setExptime(iGate, exptime[iGate]); - } - } - // restore previous specific det type config - if (det_type == defs::JUNGFRAU || det_type == defs::MOENCH) { - det.setNumberofUDPInterfaces(num_udp_interfaces); - } - if (det_type == defs::EIGER || det_type == defs::MYTHEN3) { - det.setDynamicRange(dynamic_range); - } - if (det_type == defs::MYTHEN3) { - det.setCounterMask(counter_mask); - } - } // namespace sls \ No newline at end of file diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 1956d3578..71ad69d6c 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -591,6 +591,7 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { // store each selected bit from all samples consecutively if (ctbDbitReorder) { + LOG(logINFORED) << "Reordering digital data"; size_t numBitsPerDbit = numDigitalSamples; // num bits per selected digital // Bit for all samples @@ -605,6 +606,8 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { if ((numBitsPerSample % 8) != 0) numBitsPerSample += (8 - (numBitsPerSample % 8)); totalNumBytes = (numBitsPerSample / 8) * numDigitalSamples; + LOG(logINFORED) << "total numDigital bytes without reorder:" + << totalNumBytes; } std::vector result(totalNumBytes, 0); @@ -677,11 +680,11 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { memcpy(data + nAnalogDataBytes, result.data(), totalNumBytes * sizeof(uint8_t)); - LOG(logDEBUG1) << "totalNumBytes: " << totalNumBytes - << " nAnalogDataBytes:" << nAnalogDataBytes - << " ctbDbitOffset:" << ctbDbitOffset - << " nTransceiverDataBytes:" << nTransceiverDataBytes - << " size:" << size; + LOG(logINFORED) << "totalNumBytes: " << totalNumBytes + << " nAnalogDataBytes:" << nAnalogDataBytes + << " ctbDbitOffset:" << ctbDbitOffset + << " nTransceiverDataBytes:" << nTransceiverDataBytes + << " size:" << size; } void DataProcessor::CropImage(size_t &size, char *data) { From 91f9c4fa836260a979be844ee56b5a04f345589a Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 11:25:34 +0200 Subject: [PATCH 058/103] minor printout removed --- slsDetectorSoftware/tests/Caller/test-Caller-global.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp index 84e65656e..713e9d5f9 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp @@ -119,9 +119,6 @@ void test_acquire_binary_file_size(const testFileInfo &file_info, uint64_t expected_file_size = num_frames_to_acquire * (expected_image_size + sizeof(defs::sls_receiver_header)); - std::cout << "exepected file size: " << expected_file_size - << " receiver header size :" << sizeof(defs::sls_receiver_header) - << " num frames:" << num_frames_to_acquire << std::endl; auto actual_file_size = std::filesystem::file_size(fname); REQUIRE(actual_file_size == expected_file_size); } From 5a24a79bf758ff8abc164d1bb0e8756b5b73b32e Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 11:26:38 +0200 Subject: [PATCH 059/103] typo fixed --- .../tests/Caller/test-Caller-chiptestboard.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index 2bba9b474..f4bc2a1cb 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -19,7 +19,7 @@ using test::PUT; struct testCtbAcquireInfo { defs::readoutMode readout_mode{defs::ANALOG_AND_DIGITAL}; - bool teng_giga{false}; + bool ten_giga{false}; int num_adc_samples{5000}; int num_dbit_samples{6000}; int num_trans_samples{288}; @@ -54,7 +54,7 @@ testCtbAcquireInfo get_ctb_config_state(const Detector &det) { void set_ctb_config_state(Detector &det, const testCtbAcquireInfo &ctb_config_info) { det.setReadoutMode(ctb_config_info.readout_mode); - det.setTenGiga(ctb_config_info.teng_giga); + det.setTenGiga(ctb_config_info.ten_giga); det.setNumberOfAnalogSamples(ctb_config_info.num_adc_samples); det.setNumberOfDigitalSamples(ctb_config_info.num_dbit_samples); det.setNumberOfTransceiverSamples(ctb_config_info.num_trans_samples); @@ -99,8 +99,8 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, if (test_info.readout_mode == defs::ANALOG_ONLY || test_info.readout_mode == defs::ANALOG_AND_DIGITAL) { uint32_t adc_enable_mask = - (test_info.teng_giga ? test_info.adc_enable_1g - : test_info.adc_enable_10g); + (test_info.ten_giga ? test_info.adc_enable_1g + : test_info.adc_enable_10g); int num_analog_chans = __builtin_popcount(adc_enable_mask); const int num_bytes_per_sample = 2; num_analog_bytes = From dca0edcfcc7c1ba1e98e2e4f375312ca7d1de7f4 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 11:28:19 +0200 Subject: [PATCH 060/103] removed minor printout --- .../tests/Caller/test-Caller-chiptestboard.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index f4bc2a1cb..c309f3859 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -105,7 +105,6 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, const int num_bytes_per_sample = 2; num_analog_bytes = num_analog_chans * num_bytes_per_sample * test_info.num_adc_samples; - std::cout << "[Analog Databytes: " << num_analog_bytes << ']'; } // digital channels @@ -136,7 +135,6 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, } num_digital_bytes = num_digital_chans * (num_bits_per_bit / 8); } - std::cout << "[Digital Databytes: " << num_digital_bytes << ']'; } // transceiver channels if (test_info.readout_mode == defs::TRANSCEIVER_ONLY || @@ -146,13 +144,10 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, const int num_bytes_per_channel = 8; num_transceiver_bytes = num_transceiver_chans * num_bytes_per_channel * test_info.num_trans_samples; - std::cout << "[Transceiver Databytes: " << num_transceiver_bytes << ']'; } - std::cout << std::endl; // check file size (assuming local pc) uint64_t expected_image_size = num_analog_bytes + num_digital_bytes + num_transceiver_bytes; - std::cout << "Expected image size: " << expected_image_size << std::endl; test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, expected_image_size); From d4a1044fce8bb8b0748971e3fe8dd92abed4fbbf Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 12:08:29 +0200 Subject: [PATCH 061/103] incorrect counter mask tested --- slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp index 598439407..ec1a68ef5 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp @@ -47,7 +47,7 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { // set default specific det type config det.setDynamicRange(16); int test_counter_mask = 0x1; - int num_counters = __builtin_popcount(counter_mask); + int num_counters = __builtin_popcount(test_counter_mask); det.setCounterMask(test_counter_mask); // acquire From aabec193ff483e9435d22b9e6d395868f3527b7b Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 12:10:33 +0200 Subject: [PATCH 062/103] fix 10g adc enable mask, switched with 1g --- .../tests/Caller/test-Caller-chiptestboard.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index c309f3859..27e5e89c2 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -23,8 +23,8 @@ struct testCtbAcquireInfo { int num_adc_samples{5000}; int num_dbit_samples{6000}; int num_trans_samples{288}; - uint32_t adc_enable_1g{0xFFFFFFFF}; - uint32_t adc_enable_10g{0xFFFFFFFF}; + uint32_t adc_enable_1g{0xFFFFFF00}; + uint32_t adc_enable_10g{0xFF00FFFF}; int dbit_offset{0}; std::vector dbit_list{0, 12, 2, 43}; bool dbit_reorder{false}; @@ -99,8 +99,8 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, if (test_info.readout_mode == defs::ANALOG_ONLY || test_info.readout_mode == defs::ANALOG_AND_DIGITAL) { uint32_t adc_enable_mask = - (test_info.ten_giga ? test_info.adc_enable_1g - : test_info.adc_enable_10g); + (test_info.ten_giga ? test_info.adc_enable_10g + : test_info.adc_enable_1g); int num_analog_chans = __builtin_popcount(adc_enable_mask); const int num_bytes_per_sample = 2; num_analog_bytes = From 50737694037f2fcdd59ef85a796689070a21defa Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 12:41:05 +0200 Subject: [PATCH 063/103] fixed hardcoded values of nchip nchan etc from detPArameters --- .../Caller/test-Caller-chiptestboard.cpp | 20 +++++++++++++++++++ .../tests/Caller/test-Caller-eiger.cpp | 15 +++++++++----- .../tests/Caller/test-Caller-gotthard2.cpp | 13 +++++++----- .../tests/Caller/test-Caller-jungfrau.cpp | 19 +++++++++--------- .../tests/Caller/test-Caller-moench.cpp | 17 ++++++++-------- .../tests/Caller/test-Caller-mythen3.cpp | 19 ++++++++++++------ 6 files changed, 69 insertions(+), 34 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index 27e5e89c2..42c811bef 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -260,6 +260,26 @@ TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { test_ctb_acquire_with_receiver(test_ctb_config, num_frames_to_acquire, det, caller); } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::TRANSCEIVER_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + set_ctb_config_state(det, test_ctb_config); + test_ctb_acquire_with_receiver(test_ctb_config, + num_frames_to_acquire, det, caller); + } } } diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp index b59083add..bd9c4a04e 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp @@ -57,11 +57,16 @@ TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { test_frames_caught(det, num_frames_to_acquire); // check file size (assuming local pc) - // pixels_row_per_chip * pixels_col_per_chip * num_chips * - // bytes_per_pixel - size_t expected_image_size = 256 * 256 * 2 * 2; - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); + { + detParameters par(det_type); + // data split into half due to 2 udp interfaces per half module + int num_chips = (par.nChipX / 2); + int bytes_per_pixel = (dynamic_range / 8); + size_t expected_image_size = + par.nChanX * par.nChanY * num_chips * bytes_per_pixel; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + } // restore previous state set_file_state(det, prev_file_info); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp index a747c46dd..70b781640 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp @@ -58,11 +58,14 @@ TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { test_frames_caught(det, num_frames_to_acquire); // check file size (assuming local pc) - // num_channels * num_chips * bytes_per_pixel * num_frames - size_t expected_image_size = 128 * 10 * 2; - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); - + { + detParameters par(det_type); + int bytes_per_pixel = det.getDynamicRange().squash() / 8; + size_t expected_image_size = + par.nChanX * par.nChipX * bytes_per_pixel; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + } // restore previous state set_file_state(det, prev_file_info); set_common_acquire_config_state(det, prev_det_config_info); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp index 413937632..253fa0c49 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp @@ -52,17 +52,16 @@ TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { test_frames_caught(det, num_frames_to_acquire); // check file size (assuming local pc) - size_t expected_image_size = 0; - // pixels_row_per_chip * pixels_col_per_chip * num_chips * - // bytes_per_pixel - if (num_udp_interfaces == 1) { - expected_image_size = 256 * 256 * 8 * 2; - } else { - expected_image_size = 256 * 256 * 4 * 2; + { + detParameters par(det_type); + int bytes_per_pixel = det.getDynamicRange().squash() / 8; + // if 2 udp interfaces, data split into half + size_t expected_image_size = (par.nChanX * par.nChanY * par.nChipX * + par.nChipY * bytes_per_pixel) / + num_udp_interfaces; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); } - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); - // restore previous state set_file_state(det, prev_file_info); set_common_acquire_config_state(det, prev_det_config_info); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp index 577a2f3b2..f3bb026a9 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp @@ -52,15 +52,16 @@ TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { test_frames_caught(det, num_frames_to_acquire); // check file size (assuming local pc) - size_t expected_image_size = 0; - // pixels_row * pixels_col * bytes_per_pixel - if (num_udp_interfaces == 1) { - expected_image_size = 400 * 400 * 2; - } else { - expected_image_size = 400 * 200 * 2; + { + detParameters par(det_type); + int bytes_per_pixel = det.getDynamicRange().squash() / 8; + // if 2 udp interfaces, data split into half + size_t expected_image_size = (par.nChanX * par.nChanY * par.nChipX * + par.nChipY * bytes_per_pixel) / + num_udp_interfaces; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); } - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); // restore previous state set_file_state(det, prev_file_info); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp index ec1a68ef5..7e7b5ca2b 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp @@ -45,8 +45,9 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config - det.setDynamicRange(16); - int test_counter_mask = 0x1; + int test_dynamic_range = 16; + det.setDynamicRange(test_dynamic_range); + int test_counter_mask = 0x3; int num_counters = __builtin_popcount(test_counter_mask); det.setCounterMask(test_counter_mask); @@ -57,10 +58,16 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { test_frames_caught(det, num_frames_to_acquire); // check file size (assuming local pc) - // num_channels * num_chips * num_counters - size_t expected_image_size = 128 * 10 * num_counters * 2; - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); + { + detParameters par(det_type); + int bytes_per_pixel = test_dynamic_range / 8; + int num_channels_per_counter = par.nChanX / 3; + size_t expected_image_size = num_channels_per_counter * + num_counters * par.nChipX * + bytes_per_pixel; + test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, + expected_image_size); + } // restore previous state set_file_state(det, prev_file_info); From 62a5fda33f3893016c8d65d80e3bc3d59545ff39 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 15:45:14 +0200 Subject: [PATCH 064/103] fixed ctb tests, need to fix in develoepr (if digital modfe not enabled, should not take into accoutn dbitlist or dbitoffset or dbitreorder --- .../Caller/test-Caller-chiptestboard.cpp | 317 +++++++++--------- .../tests/Caller/test-Caller-eiger.cpp | 3 + .../tests/Caller/test-Caller-global.cpp | 24 +- .../tests/Caller/test-Caller-global.h | 3 - .../tests/Caller/test-Caller-gotthard2.cpp | 3 + .../tests/Caller/test-Caller-jungfrau.cpp | 3 + .../tests/Caller/test-Caller-moench.cpp | 3 + .../tests/Caller/test-Caller-mythen3.cpp | 6 + slsReceiverSoftware/src/DataProcessor.cpp | 13 +- tests/scripts/test_simulators.py | 4 +- 10 files changed, 188 insertions(+), 191 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index 42c811bef..d9fc6972b 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -8,6 +8,7 @@ #include "sls/Result.h" #include "sls/ToString.h" +#include "sls/logger.h" #include "sls/versionAPI.h" #include "test-Caller-global.h" #include "tests/globals.h" @@ -30,6 +31,126 @@ struct testCtbAcquireInfo { bool dbit_reorder{false}; uint32_t transceiver_mask{0x3}; }; +testCtbAcquireInfo get_ctb_config_state(const Detector &det); +void set_ctb_config_state(Detector &det, + const testCtbAcquireInfo &ctb_config_info); +uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info); +void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, + int64_t num_frames_to_acquire, + Detector &det, Caller &caller); + +TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { + Detector det; + Caller caller(&det); + auto det_type = + det.getDetectorType().tsquash("Inconsistent detector types to test"); + + if (det_type == defs::CHIPTESTBOARD || + det_type == defs::XILINX_CHIPTESTBOARD) { + int num_frames_to_acquire = 2; + // all the test cases + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_list.clear(); + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } /* + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::TRANSCEIVER_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_aclogDEBUGquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + }*/ + } +} testCtbAcquireInfo get_ctb_config_state(const Detector &det) { return testCtbAcquireInfo{ @@ -66,34 +187,7 @@ void set_ctb_config_state(Detector &det, det.setTransceiverEnableMask(ctb_config_info.transceiver_mask); } -void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, - int64_t num_frames_to_acquire, - Detector &det, Caller &caller) { - - // save previous state - testFileInfo prev_file_info = get_file_state(det); - testCommonDetAcquireInfo prev_det_config_info = - // overwrite exptime if not using virtual ctb server - get_common_acquire_config_state(det); - testCtbAcquireInfo prev_ctb_config_info = get_ctb_config_state(det); - - // defaults - testFileInfo test_file_info; - set_file_state(det, test_file_info); - testCommonDetAcquireInfo det_config; - det_config.num_frames_to_acquire = num_frames_to_acquire; - set_common_acquire_config_state(det, det_config); - - // set ctb config - set_ctb_config_state(det, test_info); - - // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); - - // check frames caught - test_frames_caught(det, num_frames_to_acquire); - - // calculate image size +uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info) { uint64_t num_analog_bytes = 0, num_digital_bytes = 0, num_transceiver_bytes = 0; if (test_info.readout_mode == defs::ANALOG_ONLY || @@ -105,6 +199,7 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, const int num_bytes_per_sample = 2; num_analog_bytes = num_analog_chans * num_bytes_per_sample * test_info.num_adc_samples; + LOG(logDEBUG1) << "[Analog Databytes: " << num_analog_bytes << ']'; } // digital channels @@ -135,6 +230,7 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, } num_digital_bytes = num_digital_chans * (num_bits_per_bit / 8); } + LOG(logDEBUG1) << "[Digital Databytes: " << num_digital_bytes << ']'; } // transceiver channels if (test_info.readout_mode == defs::TRANSCEIVER_ONLY || @@ -144,10 +240,45 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, const int num_bytes_per_channel = 8; num_transceiver_bytes = num_transceiver_chans * num_bytes_per_channel * test_info.num_trans_samples; + LOG(logDEBUG1) << "[Transceiver Databytes: " << num_transceiver_bytes + << ']'; } - // check file size (assuming local pc) - uint64_t expected_image_size = + + uint64_t image_size = num_analog_bytes + num_digital_bytes + num_transceiver_bytes; + LOG(logDEBUG1) << "Expected image size: " << image_size; + return image_size; +} + +void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, + int64_t num_frames_to_acquire, + Detector &det, Caller &caller) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + // overwrite exptime if not using virtual ctb server + get_common_acquire_config_state(det); + testCtbAcquireInfo prev_ctb_config_info = get_ctb_config_state(det); + + // defaults + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set ctb config + set_ctb_config_state(det, test_info); + + // acquire + test_acquire_with_receiver(caller, std::chrono::seconds{2}); + + // check frames caught + test_frames_caught(det, num_frames_to_acquire); + + // check file size (assuming local pc) + uint64_t expected_image_size = calculate_ctb_image_size(test_info); test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, expected_image_size); @@ -157,132 +288,6 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, set_ctb_config_state(det, prev_ctb_config_info); } -TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { - Detector det; - Caller caller(&det); - auto det_type = - det.getDetectorType().tsquash("Inconsistent detector types to test"); - - if (det_type == defs::CHIPTESTBOARD || - det_type == defs::XILINX_CHIPTESTBOARD) { - int num_frames_to_acquire = 2; - // all the test cases - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - test_ctb_config.dbit_offset = 16; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_AND_DIGITAL; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; - test_ctb_config.dbit_offset = 16; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; - test_ctb_config.dbit_list.clear(); - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::DIGITAL_AND_TRANSCEIVER; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::TRANSCEIVER_ONLY; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_ONLY; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - set_ctb_config_state(det, test_ctb_config); - test_ctb_acquire_with_receiver(test_ctb_config, - num_frames_to_acquire, det, caller); - } - } -} - /* dacs */ TEST_CASE("dacname", "[.cmdcall]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp index bd9c4a04e..3053ca9e4 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp @@ -31,6 +31,7 @@ TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { get_common_acquire_config_state(det); // save previous specific det type config + auto exptime = det.getExptime().tsquash("inconsistent exptime to test"); auto n_rows = det.getReadNRows().tsquash("inconsistent number of rows to test"); auto dynamic_range = @@ -47,6 +48,7 @@ TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config + det.setExptime(std::chrono::microseconds{200}); det.setReadNRows(256); det.setDynamicRange(16); @@ -73,6 +75,7 @@ TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, prev_det_config_info); // restore previous specific det type config + det.setExptime(exptime); det.setReadNRows(n_rows); det.setDynamicRange(dynamic_range); } diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp index 713e9d5f9..0604666d4 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp @@ -137,22 +137,11 @@ void test_acquire_with_receiver(Caller &caller, std::chrono::seconds timeout) { } testCommonDetAcquireInfo get_common_acquire_config_state(const Detector &det) { - testCommonDetAcquireInfo det_config_info{ + return testCommonDetAcquireInfo{ det.getTimingMode().tsquash("Inconsistent timing mode"), det.getNumberOfFrames().tsquash("Inconsistent number of frames"), det.getNumberOfTriggers().tsquash("Inconsistent number of triggers"), - det.getPeriod().tsquash("Inconsistent period"), - {}}; - auto det_type = - det.getDetectorType().tsquash("Inconsistent detector types to test"); - if (det_type != defs::MYTHEN3) { - det_config_info.exptime[0] = - det.getExptime().tsquash("inconsistent exptime to test"); - } else { - det_config_info.exptime = - det.getExptimeForAllGates().tsquash("inconsistent exptime to test"); - } - return det_config_info; + det.getPeriod().tsquash("Inconsistent period")}; } void set_common_acquire_config_state( @@ -161,15 +150,6 @@ void set_common_acquire_config_state( det.setNumberOfFrames(det_config_info.num_frames_to_acquire); det.setNumberOfTriggers(det_config_info.num_triggers); det.setPeriod(det_config_info.period); - auto det_type = - det.getDetectorType().tsquash("Inconsistent detector types to test"); - if (det_type != defs::MYTHEN3) { - det.setExptime(det_config_info.exptime[0]); - } else { - for (int iGate = 0; iGate < 3; ++iGate) { - det.setExptime(iGate, det_config_info.exptime[iGate]); - } - } } } // namespace sls diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.h b/slsDetectorSoftware/tests/Caller/test-Caller-global.h index d7f777deb..aa08c4332 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.h +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.h @@ -25,9 +25,6 @@ struct testCommonDetAcquireInfo { int64_t num_frames_to_acquire{2}; int64_t num_triggers{1}; std::chrono::nanoseconds period{std::chrono::milliseconds{2}}; - std::array exptime{ - std::chrono::microseconds{200}, std::chrono::nanoseconds{0}, - std::chrono::nanoseconds{0}}; }; void test_valid_port_caller(const std::string &command, diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp index 70b781640..103167f07 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp @@ -31,6 +31,7 @@ TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { get_common_acquire_config_state(det); // save previous specific det type config + auto exptime = det.getExptime().tsquash("inconsistent exptime to test"); auto burst_mode = det.getBurstMode().tsquash("inconsistent burst mode to test"); auto number_of_bursts = det.getNumberOfBursts().tsquash( @@ -47,6 +48,7 @@ TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config + det.setExptime(std::chrono::microseconds{200}); det.setBurstMode(defs::CONTINUOUS_EXTERNAL); det.setNumberOfBursts(1); det.setBurstPeriod(std::chrono::milliseconds{0}); @@ -71,6 +73,7 @@ TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, prev_det_config_info); // restore previous specific det type config + det.setExptime(exptime); det.setBurstMode(burst_mode); det.setNumberOfBursts(number_of_bursts); det.setBurstPeriod(burst_period); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp index 253fa0c49..7d56a0a00 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp @@ -29,6 +29,7 @@ TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { get_common_acquire_config_state(det); // save previous specific det type config + auto exptime = det.getExptime().tsquash("inconsistent exptime to test"); auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( "inconsistent number of udp interfaces"); auto n_rows = @@ -43,6 +44,7 @@ TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config + det.setExptime(std::chrono::microseconds{200}); det.setReadNRows(512); // acquire @@ -67,6 +69,7 @@ TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, prev_det_config_info); // restore previous specific det type config + det.setExptime(exptime); det.setReadNRows(n_rows); } } diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp index f3bb026a9..52e3c27e3 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp @@ -29,6 +29,7 @@ TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { get_common_acquire_config_state(det); // save previous specific det type config + auto exptime = det.getExptime().tsquash("inconsistent exptime to test"); auto num_udp_interfaces = det.getNumberofUDPInterfaces().tsquash( "inconsistent number of udp interfaces"); auto n_rows = @@ -43,6 +44,7 @@ TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config + det.setExptime(std::chrono::microseconds{200}); det.setReadNRows(400); // acquire @@ -68,6 +70,7 @@ TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, prev_det_config_info); // restore previous specific det type config + det.setExptime(exptime); det.setReadNRows(n_rows); } } diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp index 7e7b5ca2b..13270aefe 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp @@ -31,6 +31,8 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { get_common_acquire_config_state(det); // save previous specific det type config + auto exptime = + det.getExptimeForAllGates().tsquash("inconsistent exptime to test"); auto dynamic_range = det.getDynamicRange().tsquash("inconsistent dynamic range to test"); uint32_t counter_mask = @@ -45,6 +47,7 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, det_config); // set default specific det type config + det.setExptime(-1, std::chrono::microseconds{200}); int test_dynamic_range = 16; det.setDynamicRange(test_dynamic_range); int test_counter_mask = 0x3; @@ -74,6 +77,9 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { set_common_acquire_config_state(det, prev_det_config_info); // restore previous specific det type config + for (int iGate = 0; iGate < 3; ++iGate) { + det.setExptime(iGate, exptime[iGate]); + } det.setDynamicRange(dynamic_range); det.setCounterMask(counter_mask); } diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index c9b2201b3..f9307ebe5 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -589,7 +589,6 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { // store each selected bit from all samples consecutively if (ctbDbitReorder) { - LOG(logINFORED) << "Reordering digital data"; size_t numBitsPerDbit = numDigitalSamples; // num bits per selected digital // Bit for all samples @@ -604,8 +603,6 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { if ((numBitsPerSample % 8) != 0) numBitsPerSample += (8 - (numBitsPerSample % 8)); totalNumBytes = (numBitsPerSample / 8) * numDigitalSamples; - LOG(logINFORED) << "total numDigital bytes without reorder:" - << totalNumBytes; } std::vector result(totalNumBytes, 0); @@ -678,11 +675,11 @@ void DataProcessor::ArrangeDbitData(size_t &size, char *data) { memcpy(data + nAnalogDataBytes, result.data(), totalNumBytes * sizeof(uint8_t)); - LOG(logINFORED) << "totalNumBytes: " << totalNumBytes - << " nAnalogDataBytes:" << nAnalogDataBytes - << " ctbDbitOffset:" << ctbDbitOffset - << " nTransceiverDataBytes:" << nTransceiverDataBytes - << " size:" << size; + LOG(logDEBUG1) << "nDigitalDataBytes: " << totalNumBytes + << " nAnalogDataBytes:" << nAnalogDataBytes + << " ctbDbitOffset:" << ctbDbitOffset + << " nTransceiverDataBytes:" << nTransceiverDataBytes + << " toal size:" << size; } void DataProcessor::CropImage(size_t &size, char *data) { diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index ea580c5e6..e392274ee 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -32,8 +32,8 @@ def killProcess(name): if checkIfProcessRunning(name): Log(Fore.GREEN, 'killing ' + name) p = subprocess.run(['killall', name]) - if p.returncode != 0: - raise RuntimeException('killall failed for ' + name) + #if p.returncode != 0: + # raise RuntimeException('killall failed for ' + name) else: print('process not running : ' + name) From dedab6010d0853463b6b16dd742c9568d6a02428 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Thu, 1 May 2025 15:53:37 +0200 Subject: [PATCH 065/103] only reorder bits if some sort of digital readout mode enabled --- slsReceiverSoftware/src/DataProcessor.cpp | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/slsReceiverSoftware/src/DataProcessor.cpp b/slsReceiverSoftware/src/DataProcessor.cpp index 50a41753e..a26b98d0e 100644 --- a/slsReceiverSoftware/src/DataProcessor.cpp +++ b/slsReceiverSoftware/src/DataProcessor.cpp @@ -352,16 +352,20 @@ void DataProcessor::ProcessAnImage(sls_receiver_header &header, size_t &size, if (framePadding && nump < generalData->packetsPerFrame) PadMissingPackets(header, data); - // rearrange ctb digital bits - if (!generalData->ctbDbitList.empty()) { - ArrangeDbitData(size, data); - } else if (generalData->ctbDbitReorder) { - std::vector ctbDbitList(64); - std::iota(ctbDbitList.begin(), ctbDbitList.end(), 0); - generalData->SetctbDbitList(ctbDbitList); - ArrangeDbitData(size, data); - } else if (generalData->ctbDbitOffset > 0) { - RemoveTrailingBits(size, data); + if (generalData->readoutType == slsDetectorDefs::DIGITAL_ONLY || + generalData->readoutType == slsDetectorDefs::ANALOG_AND_DIGITAL || + generalData->readoutType == slsDetectorDefs::DIGITAL_AND_TRANSCEIVER) { + // rearrange ctb digital bits + if (!generalData->ctbDbitList.empty()) { + ArrangeDbitData(size, data); + } else if (generalData->ctbDbitReorder) { + std::vector ctbDbitList(64); + std::iota(ctbDbitList.begin(), ctbDbitList.end(), 0); + generalData->SetctbDbitList(ctbDbitList); + ArrangeDbitData(size, data); + } else if (generalData->ctbDbitOffset > 0) { + RemoveTrailingBits(size, data); + } } // 'stream Image' check has to be done here before crop image From 22f2662e3ba2db17d20b8a0dfec6c8e436f1f120 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 16:19:25 +0200 Subject: [PATCH 066/103] trying to fix acquire for xilinx --- .../Caller/test-Caller-chiptestboard.cpp | 158 ------------------ .../tests/Caller/test-Caller-global.cpp | 150 +++++++++++++++++ .../tests/Caller/test-Caller-global.h | 22 +++ 3 files changed, 172 insertions(+), 158 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index d9fc6972b..22354af78 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -8,7 +8,6 @@ #include "sls/Result.h" #include "sls/ToString.h" -#include "sls/logger.h" #include "sls/versionAPI.h" #include "test-Caller-global.h" #include "tests/globals.h" @@ -18,27 +17,6 @@ namespace sls { using test::GET; using test::PUT; -struct testCtbAcquireInfo { - defs::readoutMode readout_mode{defs::ANALOG_AND_DIGITAL}; - bool ten_giga{false}; - int num_adc_samples{5000}; - int num_dbit_samples{6000}; - int num_trans_samples{288}; - uint32_t adc_enable_1g{0xFFFFFF00}; - uint32_t adc_enable_10g{0xFF00FFFF}; - int dbit_offset{0}; - std::vector dbit_list{0, 12, 2, 43}; - bool dbit_reorder{false}; - uint32_t transceiver_mask{0x3}; -}; -testCtbAcquireInfo get_ctb_config_state(const Detector &det); -void set_ctb_config_state(Detector &det, - const testCtbAcquireInfo &ctb_config_info); -uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info); -void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, - int64_t num_frames_to_acquire, - Detector &det, Caller &caller); - TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { Detector det; Caller caller(&det); @@ -152,142 +130,6 @@ TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { } } -testCtbAcquireInfo get_ctb_config_state(const Detector &det) { - return testCtbAcquireInfo{ - det.getReadoutMode().tsquash("inconsistent readout mode to test"), - det.getTenGiga().tsquash("inconsistent ten giga enable to test"), - det.getNumberOfAnalogSamples().tsquash( - "inconsistent number of analog samples to test"), - det.getNumberOfDigitalSamples().tsquash( - "inconsistent number of digital samples to test"), - det.getNumberOfTransceiverSamples().tsquash( - "inconsistent number of transceiver samples to test"), - det.getADCEnableMask().tsquash("inconsistent adc enable mask to test"), - det.getTenGigaADCEnableMask().tsquash( - "inconsistent ten giga adc enable mask to test"), - det.getRxDbitOffset().tsquash("inconsistent rx dbit offset to test"), - det.getRxDbitList().tsquash("inconsistent rx dbit list to test"), - det.getRxDbitReorder().tsquash("inconsistent rx dbit reorder to test"), - det.getTransceiverEnableMask().tsquash( - "inconsistent transceiver mask to test")}; -} - -void set_ctb_config_state(Detector &det, - const testCtbAcquireInfo &ctb_config_info) { - det.setReadoutMode(ctb_config_info.readout_mode); - det.setTenGiga(ctb_config_info.ten_giga); - det.setNumberOfAnalogSamples(ctb_config_info.num_adc_samples); - det.setNumberOfDigitalSamples(ctb_config_info.num_dbit_samples); - det.setNumberOfTransceiverSamples(ctb_config_info.num_trans_samples); - det.setADCEnableMask(ctb_config_info.adc_enable_1g); - det.setTenGigaADCEnableMask(ctb_config_info.adc_enable_10g); - det.setRxDbitOffset(ctb_config_info.dbit_offset); - det.setRxDbitList(ctb_config_info.dbit_list); - det.setRxDbitReorder(ctb_config_info.dbit_reorder); - det.setTransceiverEnableMask(ctb_config_info.transceiver_mask); -} - -uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info) { - uint64_t num_analog_bytes = 0, num_digital_bytes = 0, - num_transceiver_bytes = 0; - if (test_info.readout_mode == defs::ANALOG_ONLY || - test_info.readout_mode == defs::ANALOG_AND_DIGITAL) { - uint32_t adc_enable_mask = - (test_info.ten_giga ? test_info.adc_enable_10g - : test_info.adc_enable_1g); - int num_analog_chans = __builtin_popcount(adc_enable_mask); - const int num_bytes_per_sample = 2; - num_analog_bytes = - num_analog_chans * num_bytes_per_sample * test_info.num_adc_samples; - LOG(logDEBUG1) << "[Analog Databytes: " << num_analog_bytes << ']'; - } - - // digital channels - if (test_info.readout_mode == defs::DIGITAL_ONLY || - test_info.readout_mode == defs::ANALOG_AND_DIGITAL || - test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { - int num_digital_samples = test_info.num_dbit_samples; - if (test_info.dbit_offset > 0) { - uint64_t num_digital_bytes_reserved = - num_digital_samples * sizeof(uint64_t); - num_digital_bytes_reserved -= test_info.dbit_offset; - num_digital_samples = num_digital_bytes_reserved / sizeof(uint64_t); - } - int num_digital_chans = test_info.dbit_list.size(); - if (num_digital_chans == 0) { - num_digital_chans = 64; - } - if (!test_info.dbit_reorder) { - uint32_t num_bits_per_sample = num_digital_chans; - if (num_bits_per_sample % 8 != 0) { - num_bits_per_sample += (8 - (num_bits_per_sample % 8)); - } - num_digital_bytes = (num_bits_per_sample / 8) * num_digital_samples; - } else { - uint32_t num_bits_per_bit = num_digital_samples; - if (num_bits_per_bit % 8 != 0) { - num_bits_per_bit += (8 - (num_bits_per_bit % 8)); - } - num_digital_bytes = num_digital_chans * (num_bits_per_bit / 8); - } - LOG(logDEBUG1) << "[Digital Databytes: " << num_digital_bytes << ']'; - } - // transceiver channels - if (test_info.readout_mode == defs::TRANSCEIVER_ONLY || - test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { - int num_transceiver_chans = - __builtin_popcount(test_info.transceiver_mask); - const int num_bytes_per_channel = 8; - num_transceiver_bytes = num_transceiver_chans * num_bytes_per_channel * - test_info.num_trans_samples; - LOG(logDEBUG1) << "[Transceiver Databytes: " << num_transceiver_bytes - << ']'; - } - - uint64_t image_size = - num_analog_bytes + num_digital_bytes + num_transceiver_bytes; - LOG(logDEBUG1) << "Expected image size: " << image_size; - return image_size; -} - -void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, - int64_t num_frames_to_acquire, - Detector &det, Caller &caller) { - - // save previous state - testFileInfo prev_file_info = get_file_state(det); - testCommonDetAcquireInfo prev_det_config_info = - // overwrite exptime if not using virtual ctb server - get_common_acquire_config_state(det); - testCtbAcquireInfo prev_ctb_config_info = get_ctb_config_state(det); - - // defaults - testFileInfo test_file_info; - set_file_state(det, test_file_info); - testCommonDetAcquireInfo det_config; - det_config.num_frames_to_acquire = num_frames_to_acquire; - set_common_acquire_config_state(det, det_config); - - // set ctb config - set_ctb_config_state(det, test_info); - - // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); - - // check frames caught - test_frames_caught(det, num_frames_to_acquire); - - // check file size (assuming local pc) - uint64_t expected_image_size = calculate_ctb_image_size(test_info); - test_acquire_binary_file_size(test_file_info, num_frames_to_acquire, - expected_image_size); - - // restore previous state - set_file_state(det, prev_file_info); - set_common_acquire_config_state(det, prev_det_config_info); - set_ctb_config_state(det, prev_ctb_config_info); -} - /* dacs */ TEST_CASE("dacname", "[.cmdcall]") { diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp index 0604666d4..c04ccfb8b 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp @@ -4,6 +4,7 @@ #include "Caller.h" #include "catch.hpp" #include "sls/Detector.h" +#include "sls/logger.h" #include "tests/globals.h" namespace sls { @@ -152,4 +153,153 @@ void set_common_acquire_config_state( det.setPeriod(det_config_info.period); } +testCtbAcquireInfo get_ctb_config_state(const Detector &det) { + testCtbAcquireInfo ctb_config_info{ + det.getReadoutMode().tsquash("inconsistent readout mode to test"), + true, + det.getNumberOfAnalogSamples().tsquash( + "inconsistent number of analog samples to test"), + det.getNumberOfDigitalSamples().tsquash( + "inconsistent number of digital samples to test"), + det.getNumberOfTransceiverSamples().tsquash( + "inconsistent number of transceiver samples to test"), + 0, + det.getTenGigaADCEnableMask().tsquash( + "inconsistent ten giga adc enable mask to test"), + det.getRxDbitOffset().tsquash("inconsistent rx dbit offset to test"), + det.getRxDbitList().tsquash("inconsistent rx dbit list to test"), + det.getRxDbitReorder().tsquash("inconsistent rx dbit reorder to test"), + det.getTransceiverEnableMask().tsquash( + "inconsistent transceiver mask to test")}; + + if (det.getDetectorType().tsquash("inconsistent detector type to test") == + slsDetectorDefs::CHIPTESTBOARD) { + ctb_config_info.ten_giga = + det.getTenGiga().tsquash("inconsistent ten giga enable to test"); + ctb_config_info.adc_enable_1g = det.getADCEnableMask().tsquash( + "inconsistent adc enable mask to test"); + } + return ctb_config_info; +} + +void set_ctb_config_state(Detector &det, + const testCtbAcquireInfo &ctb_config_info) { + det.setReadoutMode(ctb_config_info.readout_mode); + if (det.getDetectorType().tsquash("inconsistent detector type to test") == + slsDetectorDefs::CHIPTESTBOARD) { + det.setTenGiga(ctb_config_info.ten_giga); + det.setADCEnableMask(ctb_config_info.adc_enable_1g); + } + det.setNumberOfAnalogSamples(ctb_config_info.num_adc_samples); + det.setNumberOfDigitalSamples(ctb_config_info.num_dbit_samples); + det.setNumberOfTransceiverSamples(ctb_config_info.num_trans_samples); + det.setTenGigaADCEnableMask(ctb_config_info.adc_enable_10g); + det.setRxDbitOffset(ctb_config_info.dbit_offset); + det.setRxDbitList(ctb_config_info.dbit_list); + det.setRxDbitReorder(ctb_config_info.dbit_reorder); + det.setTransceiverEnableMask(ctb_config_info.transceiver_mask); +} + +uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info) { + uint64_t num_analog_bytes = 0, num_digital_bytes = 0, + num_transceiver_bytes = 0; + if (test_info.readout_mode == defs::ANALOG_ONLY || + test_info.readout_mode == defs::ANALOG_AND_DIGITAL) { + uint32_t adc_enable_mask = + (test_info.ten_giga ? test_info.adc_enable_10g + : test_info.adc_enable_1g); + int num_analog_chans = __builtin_popcount(adc_enable_mask); + const int num_bytes_per_sample = 2; + num_analog_bytes = + num_analog_chans * num_bytes_per_sample * test_info.num_adc_samples; + LOG(logDEBUG1) << "[Analog Databytes: " << num_analog_bytes << ']'; + } + + // digital channels + if (test_info.readout_mode == defs::DIGITAL_ONLY || + test_info.readout_mode == defs::ANALOG_AND_DIGITAL || + test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { + int num_digital_samples = test_info.num_dbit_samples; + if (test_info.dbit_offset > 0) { + uint64_t num_digital_bytes_reserved = + num_digital_samples * sizeof(uint64_t); + num_digital_bytes_reserved -= test_info.dbit_offset; + num_digital_samples = num_digital_bytes_reserved / sizeof(uint64_t); + } + int num_digital_chans = test_info.dbit_list.size(); + if (num_digital_chans == 0) { + num_digital_chans = 64; + } + if (!test_info.dbit_reorder) { + uint32_t num_bits_per_sample = num_digital_chans; + if (num_bits_per_sample % 8 != 0) { + num_bits_per_sample += (8 - (num_bits_per_sample % 8)); + } + num_digital_bytes = (num_bits_per_sample / 8) * num_digital_samples; + } else { + uint32_t num_bits_per_bit = num_digital_samples; + if (num_bits_per_bit % 8 != 0) { + num_bits_per_bit += (8 - (num_bits_per_bit % 8)); + } + num_digital_bytes = num_digital_chans * (num_bits_per_bit / 8); + } + LOG(logDEBUG1) << "[Digital Databytes: " << num_digital_bytes << ']'; + } + // transceiver channels + if (test_info.readout_mode == defs::TRANSCEIVER_ONLY || + test_info.readout_mode == defs::DIGITAL_AND_TRANSCEIVER) { + int num_transceiver_chans = + __builtin_popcount(test_info.transceiver_mask); + const int num_bytes_per_channel = 8; + num_transceiver_bytes = num_transceiver_chans * num_bytes_per_channel * + test_info.num_trans_samples; + LOG(logDEBUG1) << "[Transceiver Databytes: " << num_transceiver_bytes + << ']'; + } + + uint64_t image_size = + num_analog_bytes + num_digital_bytes + num_transceiver_bytes; + LOG(logDEBUG1) << "Expected image size: " << image_size; + return image_size; +} + +void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, + int64_t num_frames_to_acquire, + Detector &det, Caller &caller) { + + // save previous state + testFileInfo prev_file_info = get_file_state(det); + testCommonDetAcquireInfo prev_det_config_info = + // overwrite exptime if not using virtual ctb server + get_common_acquire_config_state(det); + testCtbAcquireInfo prev_ctb_config_info = get_ctb_config_state(det); + + // defaults + testFileInfo test_file_info; + set_file_state(det, test_file_info); + testCommonDetAcquireInfo det_config; + det_config.num_frames_to_acquire = num_frames_to_acquire; + set_common_acquire_config_state(det, det_config); + + // set ctb config + set_ctb_config_state(det, test_info); + + // acquire + REQUIRE_NOTHROW( + test_acquire_with_receiver(caller, std::chrono::seconds{2})); + + // check frames caught + REQUIRE_NOTHROW(test_frames_caught(det, num_frames_to_acquire)); + + // check file size (assuming local pc) + uint64_t expected_image_size = calculate_ctb_image_size(test_info); + REQUIRE_NOTHROW(test_acquire_binary_file_size( + test_file_info, num_frames_to_acquire, expected_image_size)); + + // restore previous state + set_file_state(det, prev_file_info); + set_common_acquire_config_state(det, prev_det_config_info); + set_ctb_config_state(det, prev_ctb_config_info); +} + } // namespace sls diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.h b/slsDetectorSoftware/tests/Caller/test-Caller-global.h index aa08c4332..2d9345623 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.h +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.h @@ -27,6 +27,20 @@ struct testCommonDetAcquireInfo { std::chrono::nanoseconds period{std::chrono::milliseconds{2}}; }; +struct testCtbAcquireInfo { + defs::readoutMode readout_mode{defs::ANALOG_AND_DIGITAL}; + bool ten_giga{false}; + int num_adc_samples{5000}; + int num_dbit_samples{6000}; + int num_trans_samples{288}; + uint32_t adc_enable_1g{0xFFFFFF00}; + uint32_t adc_enable_10g{0xFF00FFFF}; + int dbit_offset{0}; + std::vector dbit_list{0, 12, 2, 43}; + bool dbit_reorder{false}; + uint32_t transceiver_mask{0x3}; +}; + void test_valid_port_caller(const std::string &command, const std::vector &arguments, int detector_id, int action); @@ -50,4 +64,12 @@ testCommonDetAcquireInfo get_common_acquire_config_state(const Detector &det); void set_common_acquire_config_state( Detector &det, const testCommonDetAcquireInfo &det_config_info); +testCtbAcquireInfo get_ctb_config_state(const Detector &det); +void set_ctb_config_state(Detector &det, + const testCtbAcquireInfo &ctb_config_info); +uint64_t calculate_ctb_image_size(const testCtbAcquireInfo &test_info); +void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, + int64_t num_frames_to_acquire, + Detector &det, Caller &caller); + } // namespace sls From 451b50dfed304480b87ac68ddc3fe2cdccda8134 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 16:42:11 +0200 Subject: [PATCH 067/103] fix for xilinx ctb virtual --- .../slsDetectorFunctionList.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/slsDetectorServers/xilinx_ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/xilinx_ctbDetectorServer/slsDetectorFunctionList.c index 77670525a..3fe36be67 100644 --- a/slsDetectorServers/xilinx_ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/xilinx_ctbDetectorServer/slsDetectorFunctionList.c @@ -1537,8 +1537,12 @@ void *start_timer(void *arg) { packetSize, packetsPerFrame)); // Generate Data - char imageData[imageSize]; + char *imageData = (char *)malloc(imageSize); memset(imageData, 0, imageSize); + if (imageData == NULL) { + LOG(logERROR, ("Can not allocate image.\n")); + return NULL; + } for (int i = 0; i < imageSize; i += sizeof(uint16_t)) { *((uint16_t *)(imageData + i)) = i; } @@ -1561,6 +1565,7 @@ void *start_timer(void *arg) { usleep(expUs); int srcOffset = 0; + int dataSent = 0; // loop packet for (int i = 0; i != packetsPerFrame; ++i) { @@ -1577,10 +1582,12 @@ void *start_timer(void *arg) { header->column = detPos[X]; // fill data + int remaining = imageSize - dataSent; + int dataSize = remaining < maxDataSize ? remaining : maxDataSize; memcpy(packetData + sizeof(sls_detector_header), - imageData + srcOffset, - (imageSize < maxDataSize ? imageSize : maxDataSize)); - srcOffset += maxDataSize; + imageData + srcOffset, dataSize); + srcOffset += dataSize; + dataSent += dataSize; sendUDPPacket(0, 0, packetData, packetSize); } From fb6ef8b8186816ff4cb575b6854521548aa979be Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 16:43:00 +0200 Subject: [PATCH 068/103] alloweing all tests --- .../Caller/test-Caller-chiptestboard.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index 22354af78..fe03a3ebe 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -108,25 +108,25 @@ TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { test_ctb_config.dbit_reorder = true; REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( test_ctb_config, num_frames_to_acquire, det, caller)); - } /* - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::TRANSCEIVER_ONLY; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - REQUIRE_NOTHROW(test_ctb_aclogDEBUGquire_with_receiver( - test_ctb_config, num_frames_to_acquire, det, caller)); - } - { - testCtbAcquireInfo test_ctb_config; - test_ctb_config.readout_mode = defs::ANALOG_ONLY; - test_ctb_config.dbit_offset = 16; - test_ctb_config.dbit_list.clear(); - test_ctb_config.dbit_reorder = true; - REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( - test_ctb_config, num_frames_to_acquire, det, caller)); - }*/ + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::TRANSCEIVER_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_aclogDEBUGquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } + { + testCtbAcquireInfo test_ctb_config; + test_ctb_config.readout_mode = defs::ANALOG_ONLY; + test_ctb_config.dbit_offset = 16; + test_ctb_config.dbit_list.clear(); + test_ctb_config.dbit_reorder = true; + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( + test_ctb_config, num_frames_to_acquire, det, caller)); + } } } From 53b90d92d784179f528483e776395ec317eb3c85 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 16:46:23 +0200 Subject: [PATCH 069/103] typo --- slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp index fe03a3ebe..955a30169 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-chiptestboard.cpp @@ -115,7 +115,7 @@ TEST_CASE("ctb_acquire_check_file_size", "[.cmdcall]") { test_ctb_config.dbit_offset = 16; test_ctb_config.dbit_list.clear(); test_ctb_config.dbit_reorder = true; - REQUIRE_NOTHROW(test_ctb_aclogDEBUGquire_with_receiver( + REQUIRE_NOTHROW(test_ctb_acquire_with_receiver( test_ctb_config, num_frames_to_acquire, det, caller)); } { From 7bc48e3111bfe803d5d1ea3f974e3b57aa4338bc Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 1 May 2025 16:55:09 +0200 Subject: [PATCH 070/103] fix for slsreceiver killed but complaining for virtual tests with script --- tests/scripts/test_simulators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index e392274ee..c1bc4a199 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -32,8 +32,8 @@ def killProcess(name): if checkIfProcessRunning(name): Log(Fore.GREEN, 'killing ' + name) p = subprocess.run(['killall', name]) - #if p.returncode != 0: - # raise RuntimeException('killall failed for ' + name) + if p.returncode != 0 and checkIfProcessRunning(name): + raise RuntimeException('killall failed for ' + name) else: print('process not running : ' + name) From fb79ba768ca6e19a3fb4366a0967c390583f4728 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 2 May 2025 11:30:08 +0200 Subject: [PATCH 071/103] fixed bug found by @AliceMazzoleni99 that for ctb server is still shown in pgrep -f if xilinx server running, so now the pid is killed and looking for any DetectorServer_virtual instead. also reset color coding after Log --- .../tests/Caller/test-Caller-eiger.cpp | 2 +- .../tests/Caller/test-Caller-global.cpp | 18 ++++-- .../tests/Caller/test-Caller-global.h | 2 +- .../tests/Caller/test-Caller-gotthard2.cpp | 2 +- .../tests/Caller/test-Caller-jungfrau.cpp | 2 +- .../tests/Caller/test-Caller-moench.cpp | 2 +- .../tests/Caller/test-Caller-mythen3.cpp | 2 +- tests/scripts/test_simulators.py | 58 +++++++++---------- 8 files changed, 46 insertions(+), 42 deletions(-) diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp index 3053ca9e4..265c39510 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-eiger.cpp @@ -53,7 +53,7 @@ TEST_CASE("eiger_acquire_check_file_size", "[.cmdcall]") { det.setDynamicRange(16); // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); + test_acquire_with_receiver(caller, det); // check frames caught test_frames_caught(det, num_frames_to_acquire); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp index c04ccfb8b..0c56ac15d 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.cpp @@ -130,10 +130,21 @@ void test_frames_caught(const Detector &det, int num_frames_to_acquire) { REQUIRE(frames_caught == num_frames_to_acquire); } -void test_acquire_with_receiver(Caller &caller, std::chrono::seconds timeout) { +void test_acquire_with_receiver(Caller &caller, const Detector &det) { REQUIRE_NOTHROW(caller.call("rx_start", {}, -1, PUT)); REQUIRE_NOTHROW(caller.call("start", {}, -1, PUT)); - std::this_thread::sleep_for(timeout); + bool idle = false; + while (!idle) { + std::ostringstream oss; + REQUIRE_NOTHROW(caller.call("status", {}, -1, GET)); + auto statusList = det.getDetectorStatus(); + if (statusList.any(defs::ERROR)) { + throw std::runtime_error("error status while acquiring"); + } + if (statusList.contains_only(defs::IDLE, defs::STOPPED)) { + idle = true; + } + } REQUIRE_NOTHROW(caller.call("rx_stop", {}, -1, PUT)); } @@ -285,8 +296,7 @@ void test_ctb_acquire_with_receiver(const testCtbAcquireInfo &test_info, set_ctb_config_state(det, test_info); // acquire - REQUIRE_NOTHROW( - test_acquire_with_receiver(caller, std::chrono::seconds{2})); + REQUIRE_NOTHROW(test_acquire_with_receiver(caller, det)); // check frames caught REQUIRE_NOTHROW(test_frames_caught(det, num_frames_to_acquire)); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-global.h b/slsDetectorSoftware/tests/Caller/test-Caller-global.h index 2d9345623..be944ecd2 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-global.h +++ b/slsDetectorSoftware/tests/Caller/test-Caller-global.h @@ -58,7 +58,7 @@ void test_acquire_binary_file_size(const testFileInfo &file_info, void test_frames_caught(const Detector &det, int num_frames_to_acquire); -void test_acquire_with_receiver(Caller &caller, std::chrono::seconds timeout); +void test_acquire_with_receiver(Caller &caller, const Detector &det); testCommonDetAcquireInfo get_common_acquire_config_state(const Detector &det); void set_common_acquire_config_state( diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp index 103167f07..c56e79634 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-gotthard2.cpp @@ -54,7 +54,7 @@ TEST_CASE("gotthard2_acquire_check_file_size", "[.cmdcall]") { det.setBurstPeriod(std::chrono::milliseconds{0}); // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); + test_acquire_with_receiver(caller, det); // check frames caught test_frames_caught(det, num_frames_to_acquire); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp index 7d56a0a00..07cabaa04 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-jungfrau.cpp @@ -48,7 +48,7 @@ TEST_CASE("jungfrau_acquire_check_file_size", "[.cmdcall]") { det.setReadNRows(512); // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); + test_acquire_with_receiver(caller, det); // check frames caught test_frames_caught(det, num_frames_to_acquire); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp index 52e3c27e3..9c79a51ab 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-moench.cpp @@ -48,7 +48,7 @@ TEST_CASE("moench_acquire_check_file_size", "[.cmdcall]") { det.setReadNRows(400); // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); + test_acquire_with_receiver(caller, det); // check frames caught test_frames_caught(det, num_frames_to_acquire); diff --git a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp index 13270aefe..c5d644b3d 100644 --- a/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp +++ b/slsDetectorSoftware/tests/Caller/test-Caller-mythen3.cpp @@ -55,7 +55,7 @@ TEST_CASE("mythen3_acquire_check_file_size", "[.cmdcall]") { det.setCounterMask(test_counter_mask); // acquire - test_acquire_with_receiver(caller, std::chrono::seconds{2}); + test_acquire_with_receiver(caller, det); // check frames caught test_frames_caught(det, num_frames_to_acquire); diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index c1bc4a199..46510fc6a 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -6,7 +6,7 @@ This file is used to start up simulators, receivers and run all the tests on the import argparse import os, sys, subprocess, time, colorama -from colorama import Fore +from colorama import Fore, Style from slsdet import Detector, detectorType, detectorSettings from slsdet.defines import DEFAULT_TCP_CNTRL_PORTNO, DEFAULT_TCP_RX_PORTNO, DEFAULT_UDP_DST_PORTNO HALFMOD2_TCP_CNTRL_PORTNO=1955 @@ -14,48 +14,42 @@ HALFMOD2_TCP_RX_PORTNO=1957 colorama.init(autoreset=True) +def Log(color, message): + print(f"{color}{message}{Style.RESET_ALL}", flush=True) + class RuntimeException (Exception): def __init__ (self, message): - super().__init__(Fore.RED + message) + super().__init__(Log(Fore.RED, message)) -def Log(color, message): - print('\n' + color + message, flush=True) - - def checkIfProcessRunning(processName): cmd = f"pgrep -f {processName}" res = subprocess.getoutput(cmd) - return bool(res.strip()) + return res.strip().splitlines() def killProcess(name): - if checkIfProcessRunning(name): - Log(Fore.GREEN, 'killing ' + name) - p = subprocess.run(['killall', name]) - if p.returncode != 0 and checkIfProcessRunning(name): - raise RuntimeException('killall failed for ' + name) - else: - print('process not running : ' + name) + pids = checkIfProcessRunning(name) + if pids: + Log(Fore.GREEN, f"Killing '{name}' processes with PIDs: {', '.join(pids)}") + for pid in pids: + try: + cmd = f"kill -9 {pid}" + p = subprocess.run(['kill', '-9', pid]) + if p.returncode != 0: + raise RuntimeException("'kill -9' failed for " + name) + except Exception as e: + Log(Fore.RED, f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") + raise + #else: + # Log(Fore.WHITE, 'process not running : ' + name) -def killAllStaleProcesses(fp): - killProcess('eigerDetectorServer_virtual') - killProcess('jungfrauDetectorServer_virtual') - killProcess('mythen3DetectorServer_virtual') - killProcess('gotthard2DetectorServer_virtual') - killProcess('ctbDetectorServer_virtual') - killProcess('moenchDetectorServer_virtual') - killProcess('xilinx_ctbDetectorServer_virtual') - killProcess('slsReceiver') - killProcess('slsMultiReceiver') - cleanSharedmemory(fp) - -def cleanup(name, fp): +def cleanup(fp): ''' kill both servers, receivers and clean shared memory ''' Log(Fore.GREEN, 'Cleaning up...') - killProcess(name + 'DetectorServer_virtual') + killProcess('DetectorServer_virtual') killProcess('slsReceiver') killProcess('slsMultiReceiver') cleanSharedmemory(fp) @@ -184,7 +178,7 @@ else: servers = args.servers -Log(Fore.WHITE, 'Arguments:\nrx_hostname: ' + args.rx_hostname + '\nsettingspath: \'' + args.settingspath + '\'') +Log(Fore.WHITE, 'Arguments:\nrx_hostname: ' + args.rx_hostname + '\nsettingspath: \'' + args.settingspath + '\nservers: \'' + ' '.join(servers) + '\'') # redirect to file @@ -207,7 +201,7 @@ with open(fname, 'w') as fp: try: startGeneralTests(fp, file_results) - killAllStaleProcesses(fp) + cleanup(fp) testError = False for server in servers: @@ -222,12 +216,12 @@ with open(fname, 'w') as fp: Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') # cmd tests for det - cleanup(server, fp) + cleanup(fp) startServer(server) startReceiver(server) loadConfig(server, args.rx_hostname, args.settingspath) startCmdTests(server, fp, file_results) - cleanup(server, fp) + cleanup(fp) except Exception as e: # redirect to terminal From 9f4298ac15127454597360225ee0f3db1b8489b1 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 2 May 2025 13:55:44 +0200 Subject: [PATCH 072/103] check if process running for kill -9 slsReceiver fail --- tests/scripts/test_simulators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index 46510fc6a..370ecc1b9 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -35,8 +35,8 @@ def killProcess(name): try: cmd = f"kill -9 {pid}" p = subprocess.run(['kill', '-9', pid]) - if p.returncode != 0: - raise RuntimeException("'kill -9' failed for " + name) + if p.returncode != 0 and bool(checkIfProcessRunning(name)): + raise RuntimeException(f"'kill -9' failed for {name} with pid {pid}") except Exception as e: Log(Fore.RED, f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") raise From eb3d51d20c0948d05bd57e22511035fd9abcce8e Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 2 May 2025 15:40:10 +0200 Subject: [PATCH 073/103] removed -9 to kill with cleanup --- tests/scripts/test_simulators.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index 370ecc1b9..c3cac14c4 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -33,10 +33,9 @@ def killProcess(name): Log(Fore.GREEN, f"Killing '{name}' processes with PIDs: {', '.join(pids)}") for pid in pids: try: - cmd = f"kill -9 {pid}" - p = subprocess.run(['kill', '-9', pid]) + p = subprocess.run(['kill', pid]) if p.returncode != 0 and bool(checkIfProcessRunning(name)): - raise RuntimeException(f"'kill -9' failed for {name} with pid {pid}") + raise RuntimeException(f"Could not kill {name} with pid {pid}") except Exception as e: Log(Fore.RED, f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") raise From 68bd9fb4f758f63d915d5af837f7377b0468035b Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 7 May 2025 15:44:32 +0200 Subject: [PATCH 074/103] frame synchonrizer fixes: typo of iterator for loop and zmg_msg_t list cleaned up before sending multi part zmq; test written for the frame synchronizer, test_simulator.py rewritten for more robustness and refactoring commonality between both scripts --- python/CMakeLists.txt | 3 + .../src/FrameSynchronizerApp.cpp | 51 ++- tests/CMakeLists.txt | 2 + tests/scripts/test_frame_synchronizer.py | 140 ++++++++ tests/scripts/test_simulators.py | 302 +++++------------- tests/scripts/utils_for_test.py | 241 ++++++++++++++ 6 files changed, 482 insertions(+), 257 deletions(-) create mode 100644 tests/scripts/test_frame_synchronizer.py create mode 100644 tests/scripts/utils_for_test.py diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 48a2d004f..eeac372f0 100755 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -65,6 +65,9 @@ configure_file( scripts/test_virtual.py ${CMAKE_BINARY_DIR}/test_virtual.py ) +configure_file(scripts/frameSynchronizerPullSocket.py + ${CMAKE_BINARY_DIR}/bin/frameSynchronizerPullSocket.py COPYONLY) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../VERSION ${CMAKE_BINARY_DIR}/bin/slsdet/VERSION ) diff --git a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp index 790a30b7b..6f8b68355 100644 --- a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp +++ b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp @@ -104,11 +104,11 @@ void zmq_free(void *data, void *hint) { delete[] static_cast(data); } void print_frames(const PortFrameMap &frame_port_map) { LOG(sls::logDEBUG) << "Printing frames"; for (const auto &it : frame_port_map) { - uint16_t udpPort = it.first; + const uint16_t udpPort = it.first; const auto &frame_map = it.second; LOG(sls::logDEBUG) << "UDP port: " << udpPort; for (const auto &frame : frame_map) { - uint64_t fnum = frame.first; + const uint64_t fnum = frame.first; const auto &msg_list = frame.second; LOG(sls::logDEBUG) << " acq index: " << fnum << '[' << msg_list.size() << ']'; @@ -127,30 +127,26 @@ std::set get_valid_fnums(const PortFrameMap &port_frame_map) { // collect all unique frame numbers from all ports std::set unique_fnums; - for (auto it = port_frame_map.begin(); it != port_frame_map.begin(); ++it) { - const FrameMap &frame_map = it->second; - for (auto frame = frame_map.begin(); frame != frame_map.end(); - ++frame) { - unique_fnums.insert(frame->first); + for (const auto &it : port_frame_map) { + const FrameMap &frame_map = it.second; + for (const auto &frame : frame_map) { + unique_fnums.insert(frame.first); } } // collect valid frame numbers for (auto &fnum : unique_fnums) { bool is_valid = true; - for (auto it = port_frame_map.begin(); it != port_frame_map.end(); - ++it) { - uint16_t port = it->first; - const FrameMap &frame_map = it->second; + for (const auto &it : port_frame_map) { + const uint16_t port = it.first; + const FrameMap &frame_map = it.second; auto frame = frame_map.find(fnum); // invalid: fnum missing in one port if (frame == frame_map.end()) { LOG(sls::logDEBUG) << "Fnum " << fnum << " is missing in port " << port; - // invalid: fnum greater than all in that port - auto last_frame = std::prev(frame_map.end()); - auto last_fnum = last_frame->first; - if (fnum > last_fnum) { + auto upper_frame = frame_map.upper_bound(fnum); + if (upper_frame == frame_map.end()) { LOG(sls::logDEBUG) << "And no larger fnum found. Fnum " << fnum << " is invalid.\n"; is_valid = false; @@ -220,29 +216,30 @@ void Correlate(FrameStatus *stat) { // sending all valid fnum data packets for (const auto &fnum : valid_fnums) { ZmqMsgList msg_list; - PortFrameMap &port_frame_map = stat->frames; - for (auto it = port_frame_map.begin(); - it != port_frame_map.end(); ++it) { - uint16_t port = it->first; - const FrameMap &frame_map = it->second; + for (const auto &it : stat->frames) { + const uint16_t port = it.first; + const FrameMap &frame_map = it.second; auto frame = frame_map.find(fnum); if (frame != frame_map.end()) { msg_list.insert(msg_list.end(), stat->frames[port][fnum].begin(), stat->frames[port][fnum].end()); - // clean up - for (zmq_msg_t *msg : stat->frames[port][fnum]) { - if (msg) { - zmq_msg_close(msg); - delete msg; - } - } stat->frames[port].erase(fnum); } } LOG(printHeadersLevel) << "Sending data packets for fnum " << fnum; zmq_send_multipart(socket, msg_list); + for (const auto &it : stat->frames) { + const uint16_t port = it.first; + // clean up + for (zmq_msg_t *msg : stat->frames[port][fnum]) { + if (msg) { + zmq_msg_close(msg); + delete msg; + } + } + } } } // sending all end packets diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f222cc599..4811f1798 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,3 +60,5 @@ include(Catch) catch_discover_tests(tests) configure_file(scripts/test_simulators.py ${CMAKE_BINARY_DIR}/bin/test_simulators.py COPYONLY) +configure_file(scripts/test_frame_synchronizer.py ${CMAKE_BINARY_DIR}/bin/test_frame_synchronizer.py COPYONLY) +configure_file(scripts/utils_for_test.py ${CMAKE_BINARY_DIR}/bin/utils_for_test.py COPYONLY) diff --git a/tests/scripts/test_frame_synchronizer.py b/tests/scripts/test_frame_synchronizer.py new file mode 100644 index 000000000..dc3c68281 --- /dev/null +++ b/tests/scripts/test_frame_synchronizer.py @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2021 Contributors to the SLS Detector Package +''' +This file is used to start up simulators, frame synchronizer, pull sockets, acquire, test and kill them finally. +''' + +import sys, time +import traceback, json + +from slsdet import Detector +from slsdet.defines import DEFAULT_TCP_RX_PORTNO + +from utils_for_test import ( + Log, + LogLevel, + RuntimeException, + checkIfProcessRunning, + killProcess, + cleanup, + cleanSharedmemory, + startProcessInBackground, + startProcessInBackgroundWithLogFile, + startDetectorVirtualServer, + loadConfig, + ParseArguments +) + +LOG_PREFIX_FNAME = '/tmp/slsFrameSynchronizer_test' +MAIN_LOG_FNAME = LOG_PREFIX_FNAME + '_log.txt' +PULL_SOCKET_PREFIX_FNAME = LOG_PREFIX_FNAME + '_pull_socket_' + + +def startFrameSynchronizerPullSocket(name, fp): + fname = PULL_SOCKET_PREFIX_FNAME + name + '.txt' + cmd = ['python', '-u', 'frameSynchronizerPullSocket.py'] + startProcessInBackgroundWithLogFile(cmd, fp, fname) + + +def startFrameSynchronizer(num_mods, fp): + cmd = ['slsFrameSynchronizer', str(DEFAULT_TCP_RX_PORTNO), str(num_mods)] + # in 10.0.0 + #cmd = ['slsFrameSynchronizer', '-p', str(DEFAULT_TCP_RX_PORTNO), '-n', str(num_mods)] + startProcessInBackground(cmd, fp) + time.sleep(1) + + +def acquire(): + Log(LogLevel.INFO, 'Acquiring') + Log(LogLevel.INFO, 'Acquiring', fp) + d = Detector() + d.acquire() + + +def testFramesCaught(name, num_frames): + d = Detector() + fnum = d.rx_framescaught[0] + if fnum != num_frames: + raise RuntimeException(f"{name} caught only {fnum}. Expected {num_frames}") + + Log(LogLevel.INFOGREEN, f'Frames caught test passed for {name}') + Log(LogLevel.INFOGREEN, f'Frames caught test passed for {name}', fp) + + +def testZmqHeadetTypeCount(name, num_mods, num_frames, fp): + + Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}") + Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}", fp) + htype_counts = { + "header": 0, + "series_end": 0, + "module": 0 + } + + try: + # get a count of each htype from file + pull_socket_fname = PULL_SOCKET_PREFIX_FNAME + name + '.txt' + with open(pull_socket_fname, 'r') as log_fp: + for line in log_fp: + line = line.strip() + if not line or not line.startswith('{'): + continue + try: + data = json.loads(line) + htype = data.get("htype") + if htype in htype_counts: + htype_counts[htype] += 1 + except json.JSONDecodeError: + continue + + # test if file contents matches expected counts + d = Detector() + num_ports_per_module = 1 if name == "gotthard2" else d.numinterfaces + total_num_frame_parts = num_ports_per_module * num_mods * num_frames + for htype, expected_count in [("header", num_mods), ("series_end", num_mods), ("module", total_num_frame_parts)]: + if htype_counts[htype] != expected_count: + msg = f"Expected {expected_count} '{htype}' entries, found {htype_counts[htype]}" + raise RuntimeException(msg) + except Exception as e: + raise RuntimeException(f'Failed to get zmq header count type. Error:{e}') + + Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}") + Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}", fp) + + +def startTestsForAll(args, fp): + for server in args.servers: + try: + Log(LogLevel.INFOBLUE, f'Synchronizer Tests for {server}') + Log(LogLevel.INFOBLUE, f'Synchronizer Tests for {server}', fp) + cleanup(fp) + startDetectorVirtualServer(server, args.num_mods, fp) + startFrameSynchronizerPullSocket(server, fp) + startFrameSynchronizer(args.num_mods, fp) + loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=args.num_mods, num_frames=args.num_frames) + acquire() + testFramesCaught(server, args.num_frames) + testZmqHeadetTypeCount(server, args.num_mods, args.num_frames, fp) + Log(LogLevel.INFO, '\n') + except Exception as e: + raise RuntimeException(f'Synchronizer Tests failed') + + Log(LogLevel.INFOGREEN, 'Passed all synchronizer tests for all detectors \n' + str(args.servers)) + + +if __name__ == '__main__': + args = ParseArguments(description='Automated tests to test frame synchronizer', default_num_mods=2) + + Log(LogLevel.INFOBLUE, '\nLog File: ' + MAIN_LOG_FNAME + '\n') + + with open(MAIN_LOG_FNAME, 'w') as fp: + try: + startTestsForAll(args, fp) + cleanup(fp) + except Exception as e: + with open(MAIN_LOG_FNAME, 'a') as fp_error: + traceback.print_exc(file=fp_error) + cleanup(fp) + Log(LogLevel.ERROR, f'Tests Failed.') + + diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index c3cac14c4..fc0a54a34 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -4,244 +4,86 @@ This file is used to start up simulators, receivers and run all the tests on them and finally kill the simulators and receivers. ''' import argparse -import os, sys, subprocess, time, colorama +import sys, subprocess, time, traceback -from colorama import Fore, Style -from slsdet import Detector, detectorType, detectorSettings -from slsdet.defines import DEFAULT_TCP_CNTRL_PORTNO, DEFAULT_TCP_RX_PORTNO, DEFAULT_UDP_DST_PORTNO -HALFMOD2_TCP_CNTRL_PORTNO=1955 -HALFMOD2_TCP_RX_PORTNO=1957 +from slsdet import Detector +from slsdet.defines import DEFAULT_TCP_RX_PORTNO -colorama.init(autoreset=True) - -def Log(color, message): - print(f"{color}{message}{Style.RESET_ALL}", flush=True) - -class RuntimeException (Exception): - def __init__ (self, message): - super().__init__(Log(Fore.RED, message)) - -def checkIfProcessRunning(processName): - cmd = f"pgrep -f {processName}" - res = subprocess.getoutput(cmd) - return res.strip().splitlines() +from utils_for_test import ( + Log, + LogLevel, + RuntimeException, + checkIfProcessRunning, + killProcess, + cleanup, + cleanSharedmemory, + startProcessInBackground, + runProcessWithLogFile, + startDetectorVirtualServer, + loadConfig, + ParseArguments +) -def killProcess(name): - pids = checkIfProcessRunning(name) - if pids: - Log(Fore.GREEN, f"Killing '{name}' processes with PIDs: {', '.join(pids)}") - for pid in pids: - try: - p = subprocess.run(['kill', pid]) - if p.returncode != 0 and bool(checkIfProcessRunning(name)): - raise RuntimeException(f"Could not kill {name} with pid {pid}") - except Exception as e: - Log(Fore.RED, f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") - raise - #else: - # Log(Fore.WHITE, 'process not running : ' + name) +LOG_PREFIX_FNAME = '/tmp/slsDetectorPackage_virtual_test' +MAIN_LOG_FNAME = LOG_PREFIX_FNAME + '_log.txt' +GENERAL_TESTS_LOG_FNAME = LOG_PREFIX_FNAME + '_results_general.txt' +CMD_TEST_LOG_PREFIX_FNAME = LOG_PREFIX_FNAME + '_results_cmd_' -def cleanup(fp): - ''' - kill both servers, receivers and clean shared memory - ''' - Log(Fore.GREEN, 'Cleaning up...') - killProcess('DetectorServer_virtual') - killProcess('slsReceiver') - killProcess('slsMultiReceiver') - cleanSharedmemory(fp) +def startReceiver(num_mods, fp): + if num_mods == 1: + cmd = ['slsReceiver'] + else: + cmd = ['slsMultiReceiver', str(DEFAULT_TCP_RX_PORTNO), str(num_mods)] + # in 10.0.0 + #cmd = ['slsMultiReceiver', '-p', str(DEFAULT_TCP_RX_PORTNO), '-n', str(num_mods)] + startProcessInBackground(cmd, fp) + time.sleep(1) -def cleanSharedmemory(fp): - Log(Fore.GREEN, 'Cleaning up shared memory...') +def startGeneralTests(fp): + fname = GENERAL_TESTS_LOG_FNAME + cmd = ['tests', '--abort', '-s'] try: - p = subprocess.run(['sls_detector_get', 'free'], stdout=fp, stderr=fp) - except: - Log(Fore.RED, 'Could not free shared memory') - raise - -def startProcessInBackground(name): - try: - # in background and dont print output - p = subprocess.Popen(name.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, restore_signals=False) - Log(Fore.GREEN, 'Starting up ' + name + ' ...') - except Exception as e: - Log(Fore.RED, f'Could not start {name}:{e}') - raise - -def startServer(name): - - startProcessInBackground(name + 'DetectorServer_virtual') - # second half - if name == 'eiger': - startProcessInBackground(name + 'DetectorServer_virtual -p' + str(HALFMOD2_TCP_CNTRL_PORTNO)) - tStartup = 6 - Log(Fore.WHITE, 'Takes ' + str(tStartup) + ' seconds... Please be patient') - time.sleep(tStartup) - -def startReceiver(name): - startProcessInBackground('slsReceiver') - # second half - if name == 'eiger': - startProcessInBackground('slsReceiver -t' + str(HALFMOD2_TCP_RX_PORTNO)) - time.sleep(2) - -def loadConfig(name, rx_hostname, settingsdir): - Log(Fore.GREEN, 'Loading config') - try: - d = Detector() - if name == 'eiger': - d.hostname = 'localhost:' + str(DEFAULT_TCP_CNTRL_PORTNO) + '+localhost:' + str(HALFMOD2_TCP_CNTRL_PORTNO) - #d.udp_dstport = {2: 50003} - # will set up for every module - d.udp_dstport = DEFAULT_UDP_DST_PORTNO - d.udp_dstport2 = DEFAULT_UDP_DST_PORTNO + 1 - d.rx_hostname = rx_hostname + ':' + str(DEFAULT_TCP_RX_PORTNO) + '+' + rx_hostname + ':' + str(HALFMOD2_TCP_RX_PORTNO) - d.udp_dstip = 'auto' - d.trimen = [4500, 5400, 6400] - d.settingspath = settingsdir + '/eiger/' - d.setThresholdEnergy(4500, detectorSettings.STANDARD) - else: - d.hostname = 'localhost' - d.rx_hostname = rx_hostname - d.udp_dstip = 'auto' - d.udp_srcip = 'auto' - if d.type == detectorType.JUNGFRAU or d.type == detectorType.MOENCH or d.type == detectorType.XILINX_CHIPTESTBOARD: - d.powerchip = 1 - if d.type == detectorType.XILINX_CHIPTESTBOARD: - d.configureTransceiver() - except: - Log(Fore.RED, 'Could not load config for ' + name) - raise - -def startCmdTests(name, fp, fname): - Log(Fore.GREEN, 'Cmd Tests for ' + name) - cmd = 'tests --abort [.cmdcall] -s -o ' + fname - try: - subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) - except subprocess.CalledProcessError as e: - pass - - with open (fname, 'r') as f: - for line in f: - if "FAILED" in line: - msg = 'Cmd tests failed for ' + name + '!!!' - sys.stdout = original_stdout - Log(Fore.RED, msg) - Log(Fore.RED, line) - sys.stdout = fp - raise Exception(msg) - - Log(Fore.GREEN, 'Cmd Tests successful for ' + name) - -def startGeneralTests(fp, fname): - Log(Fore.GREEN, 'General Tests') - cmd = 'tests --abort -s -o ' + fname - try: - subprocess.run(cmd.split(), stdout=fp, stderr=fp, check=True, text=True) - except subprocess.CalledProcessError as e: - pass - - with open (fname, 'r') as f: - for line in f: - if "FAILED" in line: - msg = 'General tests failed !!!' - sys.stdout = original_stdout - Log(Fore.RED, msg + '\n' + line) - sys.stdout = fp - raise Exception(msg) - - Log(Fore.GREEN, 'General Tests successful') - - - -# parse cmd line for rx_hostname and settingspath using the argparse library -parser = argparse.ArgumentParser(description = 'automated tests with the virtual detector servers') -parser.add_argument('rx_hostname', nargs='?', default='localhost', help = 'hostname/ip of the current machine') -parser.add_argument('settingspath', nargs='?', default='../../settingsdir', help = 'Relative or absolut path to the settingspath') -parser.add_argument('-s', '--servers', help='Detector servers to run', nargs='*') -args = parser.parse_args() - -if args.servers is None: - servers = [ - 'eiger', - 'jungfrau', - 'mythen3', - 'gotthard2', - 'ctb', - 'moench', - 'xilinx_ctb' - ] -else: - servers = args.servers - - -Log(Fore.WHITE, 'Arguments:\nrx_hostname: ' + args.rx_hostname + '\nsettingspath: \'' + args.settingspath + '\nservers: \'' + ' '.join(servers) + '\'') - - -# redirect to file -prefix_fname = '/tmp/slsDetectorPackage_virtual_test' -original_stdout = sys.stdout -original_stderr = sys.stderr -fname = prefix_fname + '_log.txt' -Log(Fore.BLUE, '\nLog File: ' + fname) - -with open(fname, 'w') as fp: - - - - # general tests - file_results = prefix_fname + '_results_general.txt' - Log(Fore.BLUE, 'General tests (results: ' + file_results + ')') - sys.stdout = fp - sys.stderr = fp - Log(Fore.BLUE, 'General tests (results: ' + file_results + ')') - - try: - startGeneralTests(fp, file_results) cleanup(fp) - - testError = False - for server in servers: - try: - # print to terminal for progress - sys.stdout = original_stdout - sys.stderr = original_stderr - file_results = prefix_fname + '_results_cmd_' + server + '.txt' - Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') - sys.stdout = fp - sys.stderr = fp - Log(Fore.BLUE, 'Cmd tests for ' + server + ' (results: ' + file_results + ')') - - # cmd tests for det - cleanup(fp) - startServer(server) - startReceiver(server) - loadConfig(server, args.rx_hostname, args.settingspath) - startCmdTests(server, fp, file_results) - cleanup(fp) - - except Exception as e: - # redirect to terminal - sys.stdout = original_stdout - sys.stderr = original_stderr - Log(Fore.RED, f'Exception caught while testing {server}. Cleaning up...') - testError = True - break - - # redirect to terminal - sys.stdout = original_stdout - sys.stderr = original_stderr - if not testError: - Log(Fore.GREEN, 'Passed all tests for virtual detectors \n' + str(servers)) - - + runProcessWithLogFile('General Tests', cmd, fp, fname) except Exception as e: - # redirect to terminal - sys.stdout = original_stdout - sys.stderr = original_stderr - Log(Fore.RED, f'Exception caught with general testing. Cleaning up...') - cleanSharedmemory(sys.stdout) - + raise RuntimeException(f'General tests failed.') from e + +def startCmdTestsForAll(args, fp): + for server in args.servers: + try: + num_mods = 2 if server == 'eiger' else 1 + fname = CMD_TEST_LOG_PREFIX_FNAME + server + '.txt' + cmd = ['tests', '--abort', '[.cmdcall]', '-s'] + + Log(LogLevel.INFOBLUE, f'Starting Cmd Tests for {server}') + cleanup(fp) + startDetectorVirtualServer(name=server, num_mods=num_mods, fp=fp) + startReceiver(num_mods, fp) + loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=num_mods) + runProcessWithLogFile('Cmd Tests for ' + server, cmd, fp, fname) + except Exception as e: + raise RuntimeException(f'Cmd Tests failed for {server}.') + + Log(LogLevel.INFOGREEN, 'Passed all tests for all detectors \n' + str(args.servers)) + + +if __name__ == '__main__': + args = ParseArguments('Automated tests with the virtual detector servers') + if args.num_mods > 1: + raise RuntimeException(f'Cannot support multiple modules at the moment (except Eiger).') + + Log(LogLevel.INFOBLUE, '\nLog File: ' + MAIN_LOG_FNAME + '\n') + + with open(MAIN_LOG_FNAME, 'w') as fp: + try: + startGeneralTests(fp) + startCmdTestsForAll(args, fp) + cleanup(fp) + except Exception as e: + with open(MAIN_LOG_FNAME, 'a') as fp_error: + traceback.print_exc(file=fp_error) + cleanup(fp) + Log(LogLevel.ERROR, f'Tests Failed.') diff --git a/tests/scripts/utils_for_test.py b/tests/scripts/utils_for_test.py new file mode 100644 index 000000000..ab1087d8d --- /dev/null +++ b/tests/scripts/utils_for_test.py @@ -0,0 +1,241 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2021 Contributors to the SLS Detector Package +''' +This file is used for common utils used for integration tests between simulators and receivers. +''' + +import sys, subprocess, time, argparse +from enum import Enum +from colorama import Fore, Style, init + +from slsdet import Detector, detectorSettings +from slsdet.defines import DEFAULT_TCP_RX_PORTNO, DEFAULT_UDP_DST_PORTNO +SERVER_START_PORTNO=1900 + +init(autoreset=True) + + +class LogLevel(Enum): + INFO = 0 + INFORED = 1 + INFOGREEN = 2 + INFOBLUE = 3 + WARNING = 4 + ERROR = 5 + DEBUG = 6 + + +LOG_LABELS = { + LogLevel.WARNING: "WARNING: ", + LogLevel.ERROR: "ERROR: ", + LogLevel.DEBUG: "DEBUG: " +} + + +LOG_COLORS = { + LogLevel.INFO: Fore.WHITE, + LogLevel.INFORED: Fore.RED, + LogLevel.INFOGREEN: Fore.GREEN, + LogLevel.INFOBLUE: Fore.BLUE, + LogLevel.WARNING: Fore.YELLOW, + LogLevel.ERROR: Fore.RED, + LogLevel.DEBUG: Fore.CYAN +} + + +def Log(level: LogLevel, message: str, stream=sys.stdout): + color = LOG_COLORS.get(level, Fore.WHITE) + label = LOG_LABELS.get(level, "") + print(f"{color}{label}{message}{Style.RESET_ALL}", file=stream, flush=True) + + +class RuntimeException (Exception): + def __init__ (self, message): + Log(LogLevel.ERROR, message) + super().__init__(message) + + +def checkIfProcessRunning(processName): + cmd = f"pgrep -f {processName}" + res = subprocess.getoutput(cmd) + return res.strip().splitlines() + + +def killProcess(name, fp): + pids = checkIfProcessRunning(name) + if pids: + Log(LogLevel.INFO, f"Killing '{name}' processes with PIDs: {', '.join(pids)}", fp) + for pid in pids: + try: + p = subprocess.run(['kill', pid]) + if p.returncode != 0 and bool(checkIfProcessRunning(name)): + raise RuntimeException(f"Could not kill {name} with pid {pid}") + except Exception as e: + raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") + #else: + # Log(LogLevel.INFO, 'process not running : ' + name) + + +def cleanSharedmemory(fp): + Log(LogLevel.INFO, 'Cleaning up shared memory', fp) + try: + p = subprocess.run(['sls_detector_get', 'free'], stdout=fp, stderr=fp) + except: + raise RuntimeException('Could not free shared memory') + + +def cleanup(fp): + Log(LogLevel.INFO, 'Cleaning up') + Log(LogLevel.INFO, 'Cleaning up', fp) + killProcess('DetectorServer_virtual', fp) + killProcess('slsReceiver', fp) + killProcess('slsMultiReceiver', fp) + killProcess('slsFrameSynchronizer', fp) + killProcess('frameSynchronizerPullSocket', fp) + cleanSharedmemory(fp) + + +def startProcessInBackground(cmd, fp): + Log(LogLevel.INFO, 'Starting up ' + ' '.join(cmd)) + Log(LogLevel.INFO, 'Starting up ' + ' '.join(cmd), fp) + try: + p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, restore_signals=False) + except Exception as e: + raise RuntimeException(f'Failed to start {cmd}:{e}') + + +def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): + Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name) + Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name, fp) + try: + with open(log_file_name, 'w') as log_fp: + subprocess.Popen(cmd, stdout=log_fp, stderr=log_fp, text=True) + except Exception as e: + raise RuntimeException(f'Failed to start {cmd}:{e}') + + +def runProcessWithLogFile(name, cmd, fp, log_file_name): + Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name) + Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name, fp) + Log(LogLevel.INFOBLUE, 'Cmd: ' + ' '.join(cmd), fp) + try: + with open(log_file_name, 'w') as log_fp: + subprocess.run(cmd, stdout=log_fp, stderr=log_fp, check=True, text=True) + except subprocess.CalledProcessError as e: + pass + except Exception as e: + Log(LogLevel.ERROR, f'Failed to run {name}:{e}', fp) + raise RuntimeException(f'Failed to run {name}:{e}') + + with open (log_file_name, 'r') as f: + for line in f: + if "FAILED" in line: + raise RuntimeException(f'{line}') + + Log(LogLevel.INFOGREEN, name + ' successful!\n') + Log(LogLevel.INFOGREEN, name + ' successful!\n', fp) + + +def startDetectorVirtualServer(name :str, num_mods, fp): + for i in range(num_mods): + port_no = SERVER_START_PORTNO + (i * 2) + cmd = [name + 'DetectorServer_virtual', '-p', str(port_no)] + startProcessInBackground(cmd, fp) + match name: + case 'jungfrau': + time.sleep(7) + case 'gotthard2': + time.sleep(5) + case _: + time.sleep(3) + + +def connectToVirtualServers(name, num_mods): + try: + d = Detector() + except Exception as e: + raise RuntimeException(f'Could not create Detector object for {name}. Error: {str(e)}') + + counts_sec = 5 + while (counts_sec != 0): + try: + d.virtual = [num_mods, SERVER_START_PORTNO] + break + except Exception as e: + # stop server still not up, wait a bit longer + if "Cannot connect to" in str(e): + Log(LogLevel.WARNING, f'Still waiting for {name} virtual server to be up...{counts_sec}s left') + time.sleep(1) + counts_sec -= 1 + else: + raise + + return d + + +def loadConfig(name, rx_hostname, settingsdir, fp, num_mods = 1, num_frames = 1): + Log(LogLevel.INFO, 'Loading config') + Log(LogLevel.INFO, 'Loading config', fp) + try: + d = connectToVirtualServers(name, num_mods) + d.udp_dstport = DEFAULT_UDP_DST_PORTNO + if name == 'eiger': + d.udp_dstport2 = DEFAULT_UDP_DST_PORTNO + 1 + + d.rx_hostname = rx_hostname + d.udp_dstip = 'auto' + if name != "eiger": + d.udp_srcip = 'auto' + + if name == "jungfrau" or name == "moench" or name == "xilinx_ctb": + d.powerchip = 1 + + if name == "xilinx_ctb": + d.configureTransceiver() + + if name == "eiger": + d.trimen = [4500, 5400, 6400] + d.settingspath = settingsdir + '/eiger/' + d.setThresholdEnergy(4500, detectorSettings.STANDARD) + + d.frames = num_frames + except Exception as e: + raise RuntimeException(f'Could not load config for {name}. Error: {str(e)}') + + +def ParseArguments(description, default_num_mods=1): + parser = argparse.ArgumentParser(description) + + parser.add_argument('rx_hostname', nargs='?', default='localhost', + help='Hostname/IP of the current machine') + parser.add_argument('settingspath', nargs='?', default='../../settingsdir', + help='Relative or absolute path to the settings directory') + parser.add_argument('-n', '--num-mods', nargs='?', default=default_num_mods, type=int, + help='Number of modules to test with') + parser.add_argument('-f', '--num-frames', nargs='?', default=1, type=int, + help='Number of frames to test with') + parser.add_argument('-s', '--servers', nargs='*', + help='Detector servers to run') + + args = parser.parse_args() + + # Set default server list if not provided + if args.servers is None: + args.servers = [ + 'eiger', + 'jungfrau', + 'mythen3', + 'gotthard2', + 'ctb', + 'moench', + 'xilinx_ctb' + ] + + Log(LogLevel.INFO, 'Arguments:\n' + + 'rx_hostname: ' + args.rx_hostname + + '\nsettingspath: \'' + args.settingspath + + '\nservers: \'' + ' '.join(args.servers) + + '\nnum_mods: \'' + str(args.num_mods) + + '\nnum_frames: \'' + str(args.num_frames) + '\'') + + return args From 64be8b1e894cf5c09d9fc3b442b446bcc69be7e7 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 7 May 2025 15:56:41 +0200 Subject: [PATCH 075/103] better error messageS --- tests/scripts/test_frame_synchronizer.py | 4 ++-- tests/scripts/test_simulators.py | 2 +- tests/scripts/utils_for_test.py | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/scripts/test_frame_synchronizer.py b/tests/scripts/test_frame_synchronizer.py index dc3c68281..38e020446 100644 --- a/tests/scripts/test_frame_synchronizer.py +++ b/tests/scripts/test_frame_synchronizer.py @@ -96,7 +96,7 @@ def testZmqHeadetTypeCount(name, num_mods, num_frames, fp): msg = f"Expected {expected_count} '{htype}' entries, found {htype_counts[htype]}" raise RuntimeException(msg) except Exception as e: - raise RuntimeException(f'Failed to get zmq header count type. Error:{e}') + raise RuntimeException(f'Failed to get zmq header count type. Error:{str(e)}') from e Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}") Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}", fp) @@ -117,7 +117,7 @@ def startTestsForAll(args, fp): testZmqHeadetTypeCount(server, args.num_mods, args.num_frames, fp) Log(LogLevel.INFO, '\n') except Exception as e: - raise RuntimeException(f'Synchronizer Tests failed') + raise RuntimeException(f'Synchronizer Tests failed') from e Log(LogLevel.INFOGREEN, 'Passed all synchronizer tests for all detectors \n' + str(args.servers)) diff --git a/tests/scripts/test_simulators.py b/tests/scripts/test_simulators.py index fc0a54a34..d6f4371d9 100644 --- a/tests/scripts/test_simulators.py +++ b/tests/scripts/test_simulators.py @@ -65,7 +65,7 @@ def startCmdTestsForAll(args, fp): loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=num_mods) runProcessWithLogFile('Cmd Tests for ' + server, cmd, fp, fname) except Exception as e: - raise RuntimeException(f'Cmd Tests failed for {server}.') + raise RuntimeException(f'Cmd Tests failed for {server}.') from e Log(LogLevel.INFOGREEN, 'Passed all tests for all detectors \n' + str(args.servers)) diff --git a/tests/scripts/utils_for_test.py b/tests/scripts/utils_for_test.py index ab1087d8d..82279a29e 100644 --- a/tests/scripts/utils_for_test.py +++ b/tests/scripts/utils_for_test.py @@ -71,7 +71,7 @@ def killProcess(name, fp): if p.returncode != 0 and bool(checkIfProcessRunning(name)): raise RuntimeException(f"Could not kill {name} with pid {pid}") except Exception as e: - raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Exception occured: [code:{e}, msg:{e.stderr}]") + raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Error: {str(e)}]") from e #else: # Log(LogLevel.INFO, 'process not running : ' + name) @@ -101,7 +101,7 @@ def startProcessInBackground(cmd, fp): try: p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, restore_signals=False) except Exception as e: - raise RuntimeException(f'Failed to start {cmd}:{e}') + raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): @@ -111,7 +111,7 @@ def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): with open(log_file_name, 'w') as log_fp: subprocess.Popen(cmd, stdout=log_fp, stderr=log_fp, text=True) except Exception as e: - raise RuntimeException(f'Failed to start {cmd}:{e}') + raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e def runProcessWithLogFile(name, cmd, fp, log_file_name): @@ -124,8 +124,8 @@ def runProcessWithLogFile(name, cmd, fp, log_file_name): except subprocess.CalledProcessError as e: pass except Exception as e: - Log(LogLevel.ERROR, f'Failed to run {name}:{e}', fp) - raise RuntimeException(f'Failed to run {name}:{e}') + Log(LogLevel.ERROR, f'Failed to run {name}:{str(e)}', fp) + raise RuntimeException(f'Failed to run {name}:{str(e)}') with open (log_file_name, 'r') as f: for line in f: @@ -154,7 +154,7 @@ def connectToVirtualServers(name, num_mods): try: d = Detector() except Exception as e: - raise RuntimeException(f'Could not create Detector object for {name}. Error: {str(e)}') + raise RuntimeException(f'Could not create Detector object for {name}. Error: {str(e)}') from e counts_sec = 5 while (counts_sec != 0): @@ -200,7 +200,7 @@ def loadConfig(name, rx_hostname, settingsdir, fp, num_mods = 1, num_frames = 1) d.frames = num_frames except Exception as e: - raise RuntimeException(f'Could not load config for {name}. Error: {str(e)}') + raise RuntimeException(f'Could not load config for {name}. Error: {str(e)}') from e def ParseArguments(description, default_num_mods=1): From 77a39b4ef2dc103bdbce5d2845e46fc8d97e8ec1 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 7 May 2025 15:58:28 +0200 Subject: [PATCH 076/103] minor --- tests/scripts/test_frame_synchronizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scripts/test_frame_synchronizer.py b/tests/scripts/test_frame_synchronizer.py index 38e020446..87c784cf7 100644 --- a/tests/scripts/test_frame_synchronizer.py +++ b/tests/scripts/test_frame_synchronizer.py @@ -44,7 +44,7 @@ def startFrameSynchronizer(num_mods, fp): time.sleep(1) -def acquire(): +def acquire(fp): Log(LogLevel.INFO, 'Acquiring') Log(LogLevel.INFO, 'Acquiring', fp) d = Detector() @@ -112,7 +112,7 @@ def startTestsForAll(args, fp): startFrameSynchronizerPullSocket(server, fp) startFrameSynchronizer(args.num_mods, fp) loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=args.num_mods, num_frames=args.num_frames) - acquire() + acquire(fp) testFramesCaught(server, args.num_frames) testZmqHeadetTypeCount(server, args.num_mods, args.num_frames, fp) Log(LogLevel.INFO, '\n') From a53873b695b54b67f077d27051d49f6e8f833286 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 7 May 2025 16:03:43 +0200 Subject: [PATCH 077/103] typo --- tests/scripts/utils_for_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scripts/utils_for_test.py b/tests/scripts/utils_for_test.py index 82279a29e..f2ca16cee 100644 --- a/tests/scripts/utils_for_test.py +++ b/tests/scripts/utils_for_test.py @@ -71,7 +71,7 @@ def killProcess(name, fp): if p.returncode != 0 and bool(checkIfProcessRunning(name)): raise RuntimeException(f"Could not kill {name} with pid {pid}") except Exception as e: - raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Error: {str(e)}]") from e + raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Error: {str(e)}") from e #else: # Log(LogLevel.INFO, 'process not running : ' + name) From 68bdd75c9c2f29ca41f8a62d81665e5f78f469ec Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 7 May 2025 16:33:50 +0200 Subject: [PATCH 078/103] moving the erasure of the fnum to after sending the zmg packets and also deleteing all old frames when end of acquisition --- .../src/FrameSynchronizerApp.cpp | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp index 6f8b68355..4fc6c39fe 100644 --- a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp +++ b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp @@ -224,20 +224,24 @@ void Correlate(FrameStatus *stat) { msg_list.insert(msg_list.end(), stat->frames[port][fnum].begin(), stat->frames[port][fnum].end()); - stat->frames[port].erase(fnum); } } LOG(printHeadersLevel) << "Sending data packets for fnum " << fnum; zmq_send_multipart(socket, msg_list); + // clean up for (const auto &it : stat->frames) { const uint16_t port = it.first; - // clean up - for (zmq_msg_t *msg : stat->frames[port][fnum]) { - if (msg) { - zmq_msg_close(msg); - delete msg; + const FrameMap &frame_map = it.second; + auto frame = frame_map.find(fnum); + if (frame != frame_map.end()) { + for (zmq_msg_t *msg : frame->second) { + if (msg) { + zmq_msg_close(msg); + delete msg; + } } + stat->frames[port].erase(fnum); } } } @@ -253,6 +257,21 @@ void Correlate(FrameStatus *stat) { } } stat->ends.clear(); + // clean up old frames + for (auto &it : stat->frames) { + FrameMap &frame_map = it.second; + for (auto &frame : frame_map) { + for (zmq_msg_t *msg : frame.second) { + if (msg) { + zmq_msg_close(msg); + delete msg; + } + } + frame.second.clear(); + } + frame_map.clear(); + } + stat->frames.clear(); } } } From 9051dae7878814c02fafcf59847adef8a13502bf Mon Sep 17 00:00:00 2001 From: Martin Mueller Date: Thu, 8 May 2025 15:40:13 +0200 Subject: [PATCH 079/103] fix bug in blackfin read access to firmware registers --- .../slsDetectorFunctionList.c | 29 +++++++++++++++++++ .../include/slsDetectorFunctionList.h | 1 + 2 files changed, 30 insertions(+) diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index d7f9e4f56..3c0d25d7c 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -15,6 +15,7 @@ #include "loadPattern.h" #include +#include #include #include #include // usleep @@ -93,6 +94,7 @@ void basictests() { LOG(logINFOBLUE, ("********* Chip Test Board Virtual Server *********\n")); #else LOG(logINFOBLUE, ("************* Chip Test Board Server *************\n")); + enableBlackfinAMCExternalAccessExtension(); initError = defineGPIOpins(initErrorMessage); if (initError == FAIL) { return; @@ -438,6 +440,33 @@ uint32_t getDetectorIP() { return res; } +void enableBlackfinAMCExternalAccessExtension() { + unsigned int value; + const char *file_path = "/sys/kernel/debug/blackfin/ebiu_amc/EBIU_AMBCTL1"; + + FILE *file = fopen(file_path, "r"); + if (!file) { + LOG(logERROR, ("Failed to read EBIU_AMBCTL1\n")); + return; + } + fscanf(file, "%x", &value); + fclose(file); + + // enable support for ARDY signal on interface to FPGA + // needed to properly translate avalon_mm_waitrequest in the CTB firmware + // https://www.analog.com/media/en/dsp-documentation/processor-manuals/bf537_hwr_Rev3.2.pdf + // page 274 + value |= 0x3; + + file = fopen(file_path, "w"); + if (!file) { + LOG(logERROR, ("Failed to enable blackfin AMC access extension\n")); + return; + } + fprintf(file, "0x%x", value); + fclose(file); +} + /* initialization */ void initControlServer() { diff --git a/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h b/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h index f572252bd..80bad5898 100644 --- a/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h +++ b/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h @@ -135,6 +135,7 @@ void setupDetector(); #if defined(CHIPTESTBOARDD) int updateDatabytesandAllocateRAM(); void updateDataBytes(); +void enableBlackfinAMCExternalAccessExtension(); #endif #if !defined(CHIPTESTBOARDD) && !defined(XILINX_CHIPTESTBOARDD) From 3ad4e01a5d45d2f35256f649b9e5c79a39f52ab8 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Thu, 15 May 2025 16:35:09 +0200 Subject: [PATCH 080/103] updates api version based on version file & converted shell script files to python --- slsDetectorServers/compileAllServers.sh | 23 +- .../compileAllServers_noAPIUpdate.sh | 19 +- slsDetectorServers/ctbDetectorServer/Makefile | 6 +- slsSupportLib/include/sls/Version.h | 2 +- slsSupportLib/include/sls/versionAPI.h | 6 +- slsSupportLib/src/Version.cpp | 3 +- slsSupportLib/tests/CMakeLists.txt | 1 + slsSupportLib/tests/test-Version.cpp | 19 ++ tests/scripts/test_frame_synchronizer.py | 140 ++++++++++ tests/scripts/utils_for_test.py | 245 ++++++++++++++++++ updateAPIVersion.py | 81 ++++++ updateAPIVersion.sh | 65 ----- updateClientAPIVersion.py | 34 +++ updateClientAPIVersion.sh | 59 ----- 14 files changed, 542 insertions(+), 161 deletions(-) mode change 100644 => 100755 slsDetectorServers/compileAllServers.sh create mode 100644 slsSupportLib/tests/test-Version.cpp create mode 100644 tests/scripts/test_frame_synchronizer.py create mode 100644 tests/scripts/utils_for_test.py create mode 100644 updateAPIVersion.py delete mode 100755 updateAPIVersion.sh create mode 100644 updateClientAPIVersion.py delete mode 100755 updateClientAPIVersion.sh diff --git a/slsDetectorServers/compileAllServers.sh b/slsDetectorServers/compileAllServers.sh old mode 100644 new mode 100755 index 1fe63aba8..a182a502c --- a/slsDetectorServers/compileAllServers.sh +++ b/slsDetectorServers/compileAllServers.sh @@ -1,17 +1,17 @@ # SPDX-License-Identifier: LGPL-3.0-or-other # Copyright (C) 2021 Contributors to the SLS Detector Package -# empty branch = developer branch in updateAPIVersion.sh -branch="" det_list=("ctbDetectorServer gotthard2DetectorServer jungfrauDetectorServer mythen3DetectorServer moenchDetectorServer - xilinx_ctbDetectorServer" + xilinx_ctbDetectorServer" ) -usage="\nUsage: compileAllServers.sh [server|all(opt)] [branch(opt)]. \n\tNo arguments mean all servers with 'developer' branch. \n\tNo 'branch' input means 'developer branch'" +usage="\nUsage: compileAllServers.sh [server|all(opt)] [update_api(opt)]. \n\tNo arguments mean all servers with 'developer' branch. \n\tupdate_api if true updates the api to version in VERSION file" +update_api=false +target=clean # arguments if [ $# -eq 0 ]; then # no argument, all servers @@ -34,15 +34,13 @@ elif [ $# -eq 1 ] || [ $# -eq 2 ]; then declare -a det=("${1}") #echo "Compiling only $1" fi - # branch + if [ $# -eq 2 ]; then - # arg in list - if [[ $det_list == *$2* ]]; then - echo -e "Invalid argument 2: $2. $usage" - return 1 + update_api=$2 + if $update_api ; then + target=version + echo "updating api to $(cat ../VERSION)" fi - branch+=$2 - #echo "with branch $branch" fi else echo -e "Too many arguments.$usage" @@ -61,8 +59,7 @@ do file="${i}_developer" echo -e "Compiling $dir [$file]" cd $dir - make clean - if make version API_BRANCH=$branch; then + if make $target; then deterror[$idet]="OK" else deterror[$idet]="FAIL" diff --git a/slsDetectorServers/compileAllServers_noAPIUpdate.sh b/slsDetectorServers/compileAllServers_noAPIUpdate.sh index e20573669..c2aade983 100644 --- a/slsDetectorServers/compileAllServers_noAPIUpdate.sh +++ b/slsDetectorServers/compileAllServers_noAPIUpdate.sh @@ -1,8 +1,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-other # Copyright (C) 2021 Contributors to the SLS Detector Package -# empty branch = developer branch in updateAPIVersion.sh -branch="" det_list=("ctbDetectorServer" "gotthard2DetectorServer" "jungfrauDetectorServer" @@ -10,14 +8,14 @@ det_list=("ctbDetectorServer" "moenchDetectorServer" "xilinx_ctbDetectorServer" ) -usage="\nUsage: compileAllServers.sh [server|all(opt)] [branch(opt)]. \n\tNo arguments mean all servers with 'developer' branch. \n\tNo 'branch' input means 'developer branch'" +usage="\nUsage: compileAllServers.sh [server|all(opt)]. \n\tNo arguments mean all servers with 'developer' branch." # arguments if [ $# -eq 0 ]; then # no argument, all servers declare -a det=${det_list[@]} echo "Compiling all servers" -elif [ $# -eq 1 ] || [ $# -eq 2 ]; then +elif [ $# -eq 1 ]; then # 'all' servers if [[ $1 == "all" ]]; then declare -a det=${det_list[@]} @@ -32,16 +30,6 @@ elif [ $# -eq 1 ] || [ $# -eq 2 ]; then declare -a det=("${1}") #echo "Compiling only $1" fi - # branch - if [ $# -eq 2 ]; then - # arg in list - if [[ $det_list == *$2* ]]; then - echo -e "Invalid argument 2: $2. $usage" - return -1 - fi - branch+=$2 - #echo "with branch $branch" - fi else echo -e "Too many arguments.$usage" return -1 @@ -59,8 +47,7 @@ do file="${i}_developer" echo -e "Compiling $dir [$file]" cd $dir - make clean - if make API_BRANCH=$branch; then + if make clean; then deterror[$idet]="OK" else deterror[$idet]="FAIL" diff --git a/slsDetectorServers/ctbDetectorServer/Makefile b/slsDetectorServers/ctbDetectorServer/Makefile index 5bdb5679d..22547fdc5 100755 --- a/slsDetectorServers/ctbDetectorServer/Makefile +++ b/slsDetectorServers/ctbDetectorServer/Makefile @@ -25,15 +25,15 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APICTB version_path=slsDetectorServers/ctbDetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) # echo $(OBJS) + cd $(current_dir) mkdir -p $(DESTDIR) $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) mv $(PROGS) $(DESTDIR) @@ -41,7 +41,7 @@ $(PROGS): $(OBJS) rm $(main_src)*.o $(md5_dir)*.o clean: - rm -rf $(DESTDIR)/$(PROGS) *.o *.gdb $(main_src)*.o $(md5_dir)*.o + cd $(current_dir) && rm -rf $(DESTDIR)/$(PROGS) *.o *.gdb $(main_src)*.o $(md5_dir)*.o diff --git a/slsSupportLib/include/sls/Version.h b/slsSupportLib/include/sls/Version.h index 33d10c27e..e4ec31631 100644 --- a/slsSupportLib/include/sls/Version.h +++ b/slsSupportLib/include/sls/Version.h @@ -11,7 +11,7 @@ class Version { private: std::string version_; std::string date_; - const std::string defaultBranch_ = "developer"; + inline static const std::string defaultBranch_[] = {"developer", "0.0.0"}; public: explicit Version(const std::string &s); diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index b9a1d824d..fb331c3cf 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package /** API versions */ -#define APILIB "developer 0x241122" -#define APIRECEIVER "developer 0x241122" -#define APICTB "developer 0x250310" +#define APILIB "0.0.0 0x250514" +#define APIRECEIVER "0.0.0 0x250515" +#define APICTB "0.0.0 0x250515" #define APIGOTTHARD2 "developer 0x250310" #define APIMOENCH "developer 0x250310" #define APIEIGER "developer 0x250310" diff --git a/slsSupportLib/src/Version.cpp b/slsSupportLib/src/Version.cpp index 2be4e6c3e..3ace46217 100644 --- a/slsSupportLib/src/Version.cpp +++ b/slsSupportLib/src/Version.cpp @@ -21,7 +21,8 @@ Version::Version(const std::string &s) { } bool Version::hasSemanticVersioning() const { - return version_ != defaultBranch_; + + return (version_ != defaultBranch_[0]) && (version_ != defaultBranch_[1]); } std::string Version::getVersion() const { return version_; } diff --git a/slsSupportLib/tests/CMakeLists.txt b/slsSupportLib/tests/CMakeLists.txt index 4fae7a346..827db97ff 100755 --- a/slsSupportLib/tests/CMakeLists.txt +++ b/slsSupportLib/tests/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test-TypeTraits.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test-UdpRxSocket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test-logger.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test-Version.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test-ZmqSocket.cpp ) diff --git a/slsSupportLib/tests/test-Version.cpp b/slsSupportLib/tests/test-Version.cpp new file mode 100644 index 000000000..9c1f1d3f8 --- /dev/null +++ b/slsSupportLib/tests/test-Version.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: LGPL-3.0-or-other +// Copyright (C) 2021 Contributors to the SLS Detector Package +#include "catch.hpp" +#include "sls/Version.h" + +namespace sls { + +TEST_CASE("check if version is semantic", "[.version]") { + + auto [version_string, has_semantic_version] = + GENERATE(std::make_tuple("developer 0x250512", false), + std::make_tuple("0.0.0 0x250512", false)); + + Version version(version_string); + + CHECK(version.hasSemanticVersioning() == has_semantic_version); +} + +} // namespace sls diff --git a/tests/scripts/test_frame_synchronizer.py b/tests/scripts/test_frame_synchronizer.py new file mode 100644 index 000000000..514044f1a --- /dev/null +++ b/tests/scripts/test_frame_synchronizer.py @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2021 Contributors to the SLS Detector Package +''' +This file is used to start up simulators, frame synchronizer, pull sockets, acquire, test and kill them finally. +''' + +import sys, time +import traceback, json + +from slsdet import Detector +from slsdet.defines import DEFAULT_TCP_RX_PORTNO + +from utils_for_test import ( + Log, + LogLevel, + RuntimeException, + checkIfProcessRunning, + killProcess, + cleanup, + cleanSharedmemory, + startProcessInBackground, + startProcessInBackgroundWithLogFile, + startDetectorVirtualServer, + loadConfig, + ParseArguments +) + +LOG_PREFIX_FNAME = '/tmp/slsFrameSynchronizer_test' +MAIN_LOG_FNAME = LOG_PREFIX_FNAME + '_log.txt' +PULL_SOCKET_PREFIX_FNAME = LOG_PREFIX_FNAME + '_pull_socket_' + + +def startFrameSynchronizerPullSocket(name, fp): + fname = PULL_SOCKET_PREFIX_FNAME + name + '.txt' + cmd = ['python', '-u', 'frameSynchronizerPullSocket.py'] + startProcessInBackgroundWithLogFile(cmd, fp, fname) + + +def startFrameSynchronizer(num_mods, fp): + cmd = ['slsFrameSynchronizer', str(DEFAULT_TCP_RX_PORTNO), str(num_mods)] + # in 10.0.0 + #cmd = ['slsFrameSynchronizer', '-p', str(DEFAULT_TCP_RX_PORTNO), '-n', str(num_mods)] + startProcessInBackground(cmd, fp) + time.sleep(1) + + +def acquire(fp, det): + Log(LogLevel.INFO, 'Acquiring') + Log(LogLevel.INFO, 'Acquiring', fp) + det.acquire() + + +def testFramesCaught(name, det, num_frames): + fnum = det.rx_framescaught[0] + if fnum != num_frames: + raise RuntimeException(f"{name} caught only {fnum}. Expected {num_frames}") + + Log(LogLevel.INFOGREEN, f'Frames caught test passed for {name}') + Log(LogLevel.INFOGREEN, f'Frames caught test passed for {name}', fp) + + +def testZmqHeadetTypeCount(name, det, num_mods, num_frames, fp): + + Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}") + Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}", fp) + htype_counts = { + "header": 0, + "series_end": 0, + "module": 0 + } + + try: + # get a count of each htype from file + pull_socket_fname = PULL_SOCKET_PREFIX_FNAME + name + '.txt' + with open(pull_socket_fname, 'r') as log_fp: + for line in log_fp: + line = line.strip() + if not line or not line.startswith('{'): + continue + try: + data = json.loads(line) + htype = data.get("htype") + if htype in htype_counts: + htype_counts[htype] += 1 + except json.JSONDecodeError: + continue + + # test if file contents matches expected counts + num_ports_per_module = 1 if name == "gotthard2" else det.numinterfaces + total_num_frame_parts = num_ports_per_module * num_mods * num_frames + for htype, expected_count in [("header", num_mods), ("series_end", num_mods), ("module", total_num_frame_parts)]: + if htype_counts[htype] != expected_count: + msg = f"Expected {expected_count} '{htype}' entries, found {htype_counts[htype]}" + raise RuntimeException(msg) + except Exception as e: + raise RuntimeException(f'Failed to get zmq header count type. Error:{str(e)}') from e + + Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}") + Log(LogLevel.INFOGREEN, f"Zmq Header type count test passed for {name}", fp) + + +def startTestsForAll(args, fp): + for server in args.servers: + try: + Log(LogLevel.INFOBLUE, f'Synchronizer Tests for {server}') + Log(LogLevel.INFOBLUE, f'Synchronizer Tests for {server}', fp) + cleanup(fp) + startDetectorVirtualServer(server, args.num_mods, fp) + startFrameSynchronizerPullSocket(server, fp) + startFrameSynchronizer(args.num_mods, fp) + d = loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=args.num_mods, num_frames=args.num_frames) + print("mac source adress: ", d.udp_srcmac) + print("source ip: ", d.udp_srcip) + print("detector type: ", d.type) + acquire(fp, d) + testFramesCaught(server, d, args.num_frames) + testZmqHeadetTypeCount(server, d, args.num_mods, args.num_frames, fp) + Log(LogLevel.INFO, '\n') + except Exception as e: + raise RuntimeException(f'Synchronizer Tests failed') from e + + Log(LogLevel.INFOGREEN, 'Passed all synchronizer tests for all detectors \n' + str(args.servers)) + + +if __name__ == '__main__': + args = ParseArguments(description='Automated tests to test frame synchronizer', default_num_mods=2) + + Log(LogLevel.INFOBLUE, '\nLog File: ' + MAIN_LOG_FNAME + '\n') + + with open(MAIN_LOG_FNAME, 'w') as fp: + try: + startTestsForAll(args, fp) + cleanup(fp) + except Exception as e: + with open(MAIN_LOG_FNAME, 'a') as fp_error: + traceback.print_exc(file=fp_error) + cleanup(fp) + Log(LogLevel.ERROR, f'Tests Failed.') + + diff --git a/tests/scripts/utils_for_test.py b/tests/scripts/utils_for_test.py new file mode 100644 index 000000000..f6fc88d55 --- /dev/null +++ b/tests/scripts/utils_for_test.py @@ -0,0 +1,245 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2021 Contributors to the SLS Detector Package +''' +This file is used for common utils used for integration tests between simulators and receivers. +''' + +import sys, subprocess, time, argparse +from enum import Enum +from colorama import Fore, Style, init + +from slsdet import Detector, detectorSettings +from slsdet.defines import DEFAULT_TCP_RX_PORTNO, DEFAULT_UDP_DST_PORTNO +SERVER_START_PORTNO=1900 + +init(autoreset=True) + + +class LogLevel(Enum): + INFO = 0 + INFORED = 1 + INFOGREEN = 2 + INFOBLUE = 3 + WARNING = 4 + ERROR = 5 + DEBUG = 6 + + +LOG_LABELS = { + LogLevel.WARNING: "WARNING: ", + LogLevel.ERROR: "ERROR: ", + LogLevel.DEBUG: "DEBUG: " +} + + +LOG_COLORS = { + LogLevel.INFO: Fore.WHITE, + LogLevel.INFORED: Fore.RED, + LogLevel.INFOGREEN: Fore.GREEN, + LogLevel.INFOBLUE: Fore.BLUE, + LogLevel.WARNING: Fore.YELLOW, + LogLevel.ERROR: Fore.RED, + LogLevel.DEBUG: Fore.CYAN +} + + +def Log(level: LogLevel, message: str, stream=sys.stdout): + color = LOG_COLORS.get(level, Fore.WHITE) + label = LOG_LABELS.get(level, "") + print(f"{color}{label}{message}{Style.RESET_ALL}", file=stream, flush=True) + + +class RuntimeException (Exception): + def __init__ (self, message): + Log(LogLevel.ERROR, message) + super().__init__(message) + + +def checkIfProcessRunning(processName): + cmd = f"pgrep -f {processName}" + res = subprocess.getoutput(cmd) + return res.strip().splitlines() + + +def killProcess(name, fp): + pids = checkIfProcessRunning(name) + if pids: + Log(LogLevel.INFO, f"Killing '{name}' processes with PIDs: {', '.join(pids)}", fp) + for pid in pids: + try: + p = subprocess.run(['kill', pid]) + if p.returncode != 0 and bool(checkIfProcessRunning(name)): + raise RuntimeException(f"Could not kill {name} with pid {pid}") + except Exception as e: + raise RuntimeException(f"Failed to kill process {name} pid:{pid}. Error: {str(e)}") from e + #else: + # Log(LogLevel.INFO, 'process not running : ' + name) + + +def cleanSharedmemory(fp): + Log(LogLevel.INFO, 'Cleaning up shared memory', fp) + try: + p = subprocess.run(['sls_detector_get', 'free'], stdout=fp, stderr=fp) + except: + raise RuntimeException('Could not free shared memory') + + +def cleanup(fp): + Log(LogLevel.INFO, 'Cleaning up') + Log(LogLevel.INFO, 'Cleaning up', fp) + killProcess('DetectorServer_virtual', fp) + killProcess('slsReceiver', fp) + killProcess('slsMultiReceiver', fp) + killProcess('slsFrameSynchronizer', fp) + killProcess('frameSynchronizerPullSocket', fp) + cleanSharedmemory(fp) + + +def startProcessInBackground(cmd, fp): + Log(LogLevel.INFO, 'Starting up ' + ' '.join(cmd)) + Log(LogLevel.INFO, 'Starting up ' + ' '.join(cmd), fp) + try: + p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, restore_signals=False) + except Exception as e: + raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e + + +def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): + Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name) + Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name, fp) + try: + with open(log_file_name, 'w') as log_fp: + subprocess.Popen(cmd, stdout=log_fp, stderr=log_fp, text=True) + except Exception as e: + raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e + + +def runProcessWithLogFile(name, cmd, fp, log_file_name): + Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name) + Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name, fp) + Log(LogLevel.INFOBLUE, 'Cmd: ' + ' '.join(cmd), fp) + try: + with open(log_file_name, 'w') as log_fp: + subprocess.run(cmd, stdout=log_fp, stderr=log_fp, check=True, text=True) + except subprocess.CalledProcessError as e: + pass + except Exception as e: + Log(LogLevel.ERROR, f'Failed to run {name}:{str(e)}', fp) + raise RuntimeException(f'Failed to run {name}:{str(e)}') + + with open (log_file_name, 'r') as f: + for line in f: + if "FAILED" in line: + raise RuntimeException(f'{line}') + + Log(LogLevel.INFOGREEN, name + ' successful!\n') + Log(LogLevel.INFOGREEN, name + ' successful!\n', fp) + + +def startDetectorVirtualServer(name :str, num_mods, fp): + for i in range(num_mods): + subprocess.run(["which", name + "DetectorServer_virtual"]) + + port_no = SERVER_START_PORTNO + (i * 2) + cmd = [name + 'DetectorServer_virtual', '-p', str(port_no)] + startProcessInBackgroundWithLogFile(cmd, fp, "/tmp/virtual_det_" + name + str(i) + ".txt") + match name: + case 'jungfrau': + time.sleep(7) + case 'gotthard2': + time.sleep(5) + case _: + time.sleep(3) + + +def connectToVirtualServers(name, num_mods): + try: + d = Detector() + except Exception as e: + raise RuntimeException(f'Could not create Detector object for {name}. Error: {str(e)}') from e + + counts_sec = 100 + while (counts_sec != 0): + try: + d.virtual = [num_mods, SERVER_START_PORTNO] + break + except Exception as e: + # stop server still not up, wait a bit longer + if "Cannot connect to" in str(e): + Log(LogLevel.WARNING, f'Still waiting for {name} virtual server to be up...{counts_sec}s left') + time.sleep(1) + counts_sec -= 1 + else: + raise + + return d + + +def loadConfig(name, rx_hostname, settingsdir, fp, num_mods = 1, num_frames = 1): + Log(LogLevel.INFO, 'Loading config') + Log(LogLevel.INFO, 'Loading config', fp) + try: + d = connectToVirtualServers(name, num_mods) + d.udp_dstport = DEFAULT_UDP_DST_PORTNO + if name == 'eiger': + d.udp_dstport2 = DEFAULT_UDP_DST_PORTNO + 1 + + d.rx_hostname = rx_hostname + d.udp_dstip = 'auto' + if name != "eiger": + d.udp_srcip = 'auto' + + if name == "jungfrau" or name == "moench" or name == "xilinx_ctb": + d.powerchip = 1 + + if name == "xilinx_ctb": + d.configureTransceiver() + + if name == "eiger": + d.trimen = [4500, 5400, 6400] + d.settingspath = settingsdir + '/eiger/' + d.setThresholdEnergy(4500, detectorSettings.STANDARD) + + d.frames = num_frames + except Exception as e: + raise RuntimeException(f'Could not load config for {name}. Error: {str(e)}') from e + + return d + + +def ParseArguments(description, default_num_mods=1): + parser = argparse.ArgumentParser(description) + + parser.add_argument('rx_hostname', nargs='?', default='localhost', + help='Hostname/IP of the current machine') + parser.add_argument('settingspath', nargs='?', default='../../settingsdir', + help='Relative or absolute path to the settings directory') + parser.add_argument('-n', '--num-mods', nargs='?', default=default_num_mods, type=int, + help='Number of modules to test with') + parser.add_argument('-f', '--num-frames', nargs='?', default=1, type=int, + help='Number of frames to test with') + parser.add_argument('-s', '--servers', nargs='*', + help='Detector servers to run') + + args = parser.parse_args() + + # Set default server list if not provided + if args.servers is None: + args.servers = [ + 'eiger', + 'jungfrau', + 'mythen3', + 'gotthard2', + 'ctb', + 'moench', + 'xilinx_ctb' + ] + + Log(LogLevel.INFO, 'Arguments:\n' + + 'rx_hostname: ' + args.rx_hostname + + '\nsettingspath: \'' + args.settingspath + + '\nservers: \'' + ' '.join(args.servers) + + '\nnum_mods: \'' + str(args.num_mods) + + '\nnum_frames: \'' + str(args.num_frames) + '\'') + + return args diff --git a/updateAPIVersion.py b/updateAPIVersion.py new file mode 100644 index 000000000..934df081d --- /dev/null +++ b/updateAPIVersion.py @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2025 Contributors to the SLS Detector Package +""" +Script to update API VERSION file based on the version in VERSION file. +""" + +import argparse +import sys +import os +import re +import time +from datetime import datetime + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +API_FILE = SCRIPT_DIR + "/slsSupportLib/include/sls/versionAPI.h" + +VERSION_FILE = SCRIPT_DIR + "/VERSION" + +parser = argparse.ArgumentParser(description = 'updates API version') +parser.add_argument('api_module_name', choices=["APILIB", "APIRECEIVER", "APICTB", "APIGOTTHARD2", "APIMOENCH", "APIEIGER", "APIXILINXCTB", "APIJUNGFRAU", "APIMYTHEN3"], help = 'module name to change api version options are: ["APILIB", "APIRECEIVER", "APICTB", "APIGOTTHARD2", "APIMOENCH", "APIEIGER", "APIXILINXCTB", "APIJUNGFRAU", "APIMYTHEN3"]') +parser.add_argument('api_dir', help = 'Relative or absolut path to the module code') + +def update_api_file(new_api : str, api_module_name : str, api_file_name : str): + + regex_pattern = re.compile(rf'#define\s+{api_module_name}\s+') + with open(api_file_name, "r") as api_file: + lines = api_file.readlines() + + with open(api_file_name, "w") as api_file: + for line in lines: + if regex_pattern.match(line): + api_file.write(f'#define {api_module_name} "{new_api}"\n') + else: + api_file.write(line) + +def get_latest_modification_date(directory : str): + latest_time = 0 + latest_date = None + + for root, dirs, files in os.walk(directory): + for file in files: + if file.endswith(".o"): + continue + full_path = os.path.join(root, file) + try: + mtime = os.path.getmtime(full_path) + if mtime > latest_time: + latest_time = mtime + except FileNotFoundError: + continue + + latest_date = datetime.fromtimestamp(latest_time).strftime("%y%m%d") + + return latest_date + + +def update_api_version(api_module_name : str, api_dir : str): + api_date = get_latest_modification_date(api_dir) + api_date = "0x"+str(api_date) + + with open(VERSION_FILE, "r") as version_file: + api_version = version_file.read().strip() + + api_version = api_version + " " + api_date #not sure if we should give an argument option version_branch + + update_api_file(api_version, api_module_name, API_FILE) + + print(f"updated {api_module_name} api version to: {api_version}") + +if __name__ == "__main__": + + args = parser.parse_args() + + api_dir = SCRIPT_DIR + "/" + args.api_dir + + + update_api_version(args.api_module_name, api_dir) + + diff --git a/updateAPIVersion.sh b/updateAPIVersion.sh deleted file mode 100755 index 8c45e0c48..000000000 --- a/updateAPIVersion.sh +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package -usage="\nUsage: updateAPIVersion.sh [API_NAME] [API_DIR] [API_BRANCH(opt)]." - -if [ $# -lt 2 ]; then - echo -e "Requires atleast 2 arguments. $usage" - return [-1] -fi - -API_NAME=$1 -PACKAGE_DIR=$PWD -API_DIR=$PACKAGE_DIR/$2 -API_FILE=$PACKAGE_DIR/slsSupportLib/include/sls/versionAPI.h -CURR_DIR=$PWD - -if [ ! -d "$API_DIR" ]; then - echo "[API_DIR] does not exist. $usage" - return [-1] -fi - -#go to directory -cd $API_DIR - -#deleting line from file -NUM=$(sed -n '/'$API_NAME' /=' $API_FILE) -#echo $NUM - - -if [ "$NUM" -gt 0 ]; then - sed -i ${NUM}d $API_FILE -fi - -#find new API date -API_DATE="find . -printf \"%T@ %CY-%Cm-%Cd\n\"| sort -nr | cut -d' ' -f2- | egrep -v '(\.)o' | head -n 1" - -API_DATE=`eval $API_DATE` - -API_DATE=$(sed "s/-//g" <<< $API_DATE | awk '{print $1;}' ) - -#extracting only date -API_DATE=${API_DATE:2:6} - -#prefix of 0x -API_DATE=${API_DATE/#/0x} -echo "date="$API_DATE - - -# API_VAL concatenates branch and date -API_VAL="" -# API branch is defined (3rd argument) -if [ $# -eq 3 ]; then - API_BRANCH=$3 - echo "branch="$API_BRANCH - API_VAL+="\"$API_BRANCH $API_DATE\"" -else - # API branch not defined (default is developer) - echo "branch=developer" - API_VAL+="\"developer $API_DATE\"" -fi - -#copy it to versionAPI.h -echo "#define "$API_NAME $API_VAL >> $API_FILE - -#go back to original directory -cd $CURR_DIR diff --git a/updateClientAPIVersion.py b/updateClientAPIVersion.py new file mode 100644 index 000000000..d7764cff9 --- /dev/null +++ b/updateClientAPIVersion.py @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: LGPL-3.0-or-other +# Copyright (C) 2025 Contributors to the SLS Detector Package +""" +Script to update API VERSION for slsReceiverSoftware or slsDetectorSoftware +""" + +import argparse +import os + +from updateAPIVersion import update_api_version + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +parser = argparse.ArgumentParser(description = 'updates API version') +parser.add_argument('module_name', nargs="?", choices=["slsDetectorSoftware", "slsReceiverSoftware", "all"], default="all", help = 'module name to change api version options are: ["slsDetectorSoftware", "slsReceiverSoftware, "all"]') + +if __name__ == "__main__": + args = parser.parse_args() + + if args.module_name == "all": + client_names = ["APILIB", "APIRECEIVER"] + client_directories = [SCRIPT_DIR+"/slsDetectorSoftware", SCRIPT_DIR+"/slsReceiverSoftware"] + elif args.module_name == "slsDetectorSoftware": + client_names = ["APILIB"] + client_directories = [SCRIPT_DIR+"/slsDetectorSoftware"] + else: + client_names = ["APIRECEIVER"] + client_directories = [SCRIPT_DIR+"/slsReceiverSoftware"] + + for client_name, client_directory in zip(client_names, client_directories): + update_api_version(client_name, client_directory) + + + diff --git a/updateClientAPIVersion.sh b/updateClientAPIVersion.sh deleted file mode 100755 index bed281622..000000000 --- a/updateClientAPIVersion.sh +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package -branch="" -client_list=("slsDetectorSoftware" "slsReceiverSoftware") -usage="\nUsage: updateClientAPI.sh [all|slsDetectorSoftware|slsReceiverSoftware] [branch]. \n\tNo arguments means all with 'developer' branch. \n\tNo 'branch' input means 'developer branch'" - -# arguments -if [ $# -eq 0 ]; then - declare -a client=${client_list[@]} - echo "API Versioning all" -elif [ $# -eq 1 ] || [ $# -eq 2 ]; then - # 'all' client - if [[ $1 == "all" ]]; then - declare -a client=${client_list[@]} - echo "API Versioning all" - else - # only one server - if [[ $client_list != *$1* ]]; then - echo -e "Invalid argument 1: $1. $usage" - return -1 - fi - declare -a client=("${1}") - #echo "Versioning only $1" - fi - if [ $# -eq 2 ]; then - if [[ $client_list == *$2* ]]; then - echo -e "Invalid argument 2: $2. $usage" - return -1 - fi - branch+=$2 - #echo "with branch $branch" - fi -else - echo -e "Too many arguments.$usage" - return -1 -fi - -#echo "list is: ${client[@]}" - -# versioning each client -for i in ${client[@]} -do - dir=$i - case $dir in - slsDetectorSoftware) - declare -a name=APILIB - ;; - slsReceiverSoftware) - declare -a name=APIRECEIVER - ;; - *) - echo -n "unknown client argument $i" - return -1 - ;; - esac - echo -e "Versioning $dir [$name]" - ./updateAPIVersion.sh $name $dir $branch -done - From b4c8fc1765b65378705cff3f825f346930a0f02a Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Thu, 15 May 2025 17:08:27 +0200 Subject: [PATCH 081/103] updated all makefiles --- slsDetectorServers/compileAllServers.sh | 1 + .../compileAllServers_noAPIUpdate.sh | 73 ------------------- slsDetectorServers/compileEigerServer.sh | 18 +++-- .../eigerDetectorServer/Makefile | 3 +- .../gotthard2DetectorServer/Makefile | 3 +- .../jungfrauDetectorServer/Makefile | 3 +- .../moenchDetectorServer/Makefile | 3 +- .../mythen3DetectorServer/Makefile | 3 +- .../xilinx_ctbDetectorServer/Makefile | 3 +- 9 files changed, 19 insertions(+), 91 deletions(-) delete mode 100644 slsDetectorServers/compileAllServers_noAPIUpdate.sh mode change 100644 => 100755 slsDetectorServers/compileEigerServer.sh diff --git a/slsDetectorServers/compileAllServers.sh b/slsDetectorServers/compileAllServers.sh index a182a502c..5d184c8da 100755 --- a/slsDetectorServers/compileAllServers.sh +++ b/slsDetectorServers/compileAllServers.sh @@ -59,6 +59,7 @@ do file="${i}_developer" echo -e "Compiling $dir [$file]" cd $dir + make clean if make $target; then deterror[$idet]="OK" else diff --git a/slsDetectorServers/compileAllServers_noAPIUpdate.sh b/slsDetectorServers/compileAllServers_noAPIUpdate.sh deleted file mode 100644 index c2aade983..000000000 --- a/slsDetectorServers/compileAllServers_noAPIUpdate.sh +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package - -det_list=("ctbDetectorServer" - "gotthard2DetectorServer" - "jungfrauDetectorServer" - "mythen3DetectorServer" - "moenchDetectorServer" - "xilinx_ctbDetectorServer" - ) -usage="\nUsage: compileAllServers.sh [server|all(opt)]. \n\tNo arguments mean all servers with 'developer' branch." - -# arguments -if [ $# -eq 0 ]; then - # no argument, all servers - declare -a det=${det_list[@]} - echo "Compiling all servers" -elif [ $# -eq 1 ]; then - # 'all' servers - if [[ $1 == "all" ]]; then - declare -a det=${det_list[@]} - echo "Compiling all servers" - else - # only one server - # arg not in list - if [[ $det_list != *$1* ]]; then - echo -e "Invalid argument 1: $1. $usage" - return -1 - fi - declare -a det=("${1}") - #echo "Compiling only $1" - fi -else - echo -e "Too many arguments.$usage" - return -1 -fi - -declare -a deterror=("OK" "OK" "OK" "OK" "OK" "OK") - -echo -e "list is ${det[@]}" - -# compile each server -idet=0 -for i in ${det[@]} -do - dir=$i - file="${i}_developer" - echo -e "Compiling $dir [$file]" - cd $dir - if make clean; then - deterror[$idet]="OK" - else - deterror[$idet]="FAIL" - fi - mv bin/$dir bin/$file - git add -f bin/$file - cp bin/$file /tftpboot/ - cd .. - echo -e "\n\n" - ((++idet)) -done - -echo -e "Results:" -idet=0 -for i in ${det[@]} -do - printf "%s\t\t= %s\n" "$i" "${deterror[$idet]}" - ((++idet)) -done - - - - diff --git a/slsDetectorServers/compileEigerServer.sh b/slsDetectorServers/compileEigerServer.sh old mode 100644 new mode 100755 index 35aff3f80..37aefee6a --- a/slsDetectorServers/compileEigerServer.sh +++ b/slsDetectorServers/compileEigerServer.sh @@ -3,21 +3,27 @@ deterror="OK" dir="eigerDetectorServer" file="${dir}_developer" -branch="" + +usage="\nUsage: compileAllServers.sh [update_api(opt)]. \n\t update_api if true updates the api to version in VERSION file" + +update_api=false +target=clean # arguments if [ $# -eq 1 ]; then - branch+=$1 - #echo "with branch $branch" + update_api=$1 + if $update_api ; then + target=version + echo "updating api to $(cat ../VERSION)" + fi elif [ ! $# -eq 0 ]; then - echo -e "Only one optional argument allowed for branch." + echo -e "Only one optional argument allowed for update_api." return -1 fi echo -e "Compiling $dir [$file]" cd $dir -make clean -if make version API_BRANCH=$branch; then +if make $target; then deterror="OK" else deterror="FAIL" diff --git a/slsDetectorServers/eigerDetectorServer/Makefile b/slsDetectorServers/eigerDetectorServer/Makefile index 5d896249f..63910c6f9 100755 --- a/slsDetectorServers/eigerDetectorServer/Makefile +++ b/slsDetectorServers/eigerDetectorServer/Makefile @@ -25,11 +25,10 @@ version: clean versioning $(PROGS) #hv9m_blackfin_server boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIEIGER version_path=slsDetectorServers/eigerDetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) diff --git a/slsDetectorServers/gotthard2DetectorServer/Makefile b/slsDetectorServers/gotthard2DetectorServer/Makefile index f5fa63f14..b5670a658 100755 --- a/slsDetectorServers/gotthard2DetectorServer/Makefile +++ b/slsDetectorServers/gotthard2DetectorServer/Makefile @@ -24,11 +24,10 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIGOTTHARD2 version_path=slsDetectorServers/gotthard2DetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) diff --git a/slsDetectorServers/jungfrauDetectorServer/Makefile b/slsDetectorServers/jungfrauDetectorServer/Makefile index b54c0c188..881f2e660 100755 --- a/slsDetectorServers/jungfrauDetectorServer/Makefile +++ b/slsDetectorServers/jungfrauDetectorServer/Makefile @@ -24,11 +24,10 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIJUNGFRAU version_path=slsDetectorServers/jungfrauDetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) diff --git a/slsDetectorServers/moenchDetectorServer/Makefile b/slsDetectorServers/moenchDetectorServer/Makefile index 5f7457c19..71ebbf4fd 100755 --- a/slsDetectorServers/moenchDetectorServer/Makefile +++ b/slsDetectorServers/moenchDetectorServer/Makefile @@ -24,11 +24,10 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIMOENCH version_path=slsDetectorServers/moenchDetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) diff --git a/slsDetectorServers/mythen3DetectorServer/Makefile b/slsDetectorServers/mythen3DetectorServer/Makefile index fb03c9d12..b6d68fac0 100755 --- a/slsDetectorServers/mythen3DetectorServer/Makefile +++ b/slsDetectorServers/mythen3DetectorServer/Makefile @@ -25,11 +25,10 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIMYTHEN3 version_path=slsDetectorServers/mythen3DetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) diff --git a/slsDetectorServers/xilinx_ctbDetectorServer/Makefile b/slsDetectorServers/xilinx_ctbDetectorServer/Makefile index 59cf0fe37..77b03aeb0 100755 --- a/slsDetectorServers/xilinx_ctbDetectorServer/Makefile +++ b/slsDetectorServers/xilinx_ctbDetectorServer/Makefile @@ -36,11 +36,10 @@ version: clean versioning $(PROGS) boot: $(OBJS) -version_branch=$(API_BRANCH) version_name=APIXILINXCTB version_path=slsDetectorServers/xilinx_ctbDetectorServer versioning: - cd ../../ && echo $(PWD) && echo `tput setaf 6; ./updateAPIVersion.sh $(version_name) $(version_path) $(version_branch); tput sgr0;` + cd ../../ && echo $(PWD) && echo `tput setaf 6; python updateAPIVersion.py $(version_name) $(version_path); tput sgr0;` $(PROGS): $(OBJS) From 16659375405dc3a0bf35c799278951eb9996cb50 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 19 May 2025 13:19:32 +0200 Subject: [PATCH 082/103] refactoring code and compiling binary --- .../bin/ctbDetectorServer_developer | Bin 329988 -> 330700 bytes .../slsDetectorFunctionList.c | 30 ++++++++++-------- .../slsDetectorServer_defs.h | 8 ++--- .../slsDetectorServer/include/blackfin.h | 17 ++++++++++ .../include/slsDetectorFunctionList.h | 5 ++- slsSupportLib/include/sls/versionAPI.h | 2 +- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/slsDetectorServers/ctbDetectorServer/bin/ctbDetectorServer_developer b/slsDetectorServers/ctbDetectorServer/bin/ctbDetectorServer_developer index 764bdadaab57de1a0927eb792616f71005533387..e5f92d113e6218e884e36e25eb11d86211b93f0c 100755 GIT binary patch delta 148214 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
q0|t?~S)^f_F{ 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 delta 147824 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 diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c index 3c0d25d7c..e7f914ca1 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorFunctionList.c @@ -94,7 +94,10 @@ void basictests() { LOG(logINFOBLUE, ("********* Chip Test Board Virtual Server *********\n")); #else LOG(logINFOBLUE, ("************* Chip Test Board Server *************\n")); - enableBlackfinAMCExternalAccessExtension(); + initError = enableBlackfinAMCExternalAccessExtension(initErrorMessage); + if (initError == FAIL) { + return; + } initError = defineGPIOpins(initErrorMessage); if (initError == FAIL) { return; @@ -440,31 +443,30 @@ uint32_t getDetectorIP() { return res; } -void enableBlackfinAMCExternalAccessExtension() { +int enableBlackfinAMCExternalAccessExtension(char *mess) { unsigned int value; - const char *file_path = "/sys/kernel/debug/blackfin/ebiu_amc/EBIU_AMBCTL1"; - + const char *file_path = BFIN_AMC_ACCESS_EXTENSION_FNAME; FILE *file = fopen(file_path, "r"); if (!file) { - LOG(logERROR, ("Failed to read EBIU_AMBCTL1\n")); - return; + strcpy(mess, "Failed to enable blackfin AMC access extension. Could " + "not read EBIU_AMBCTL1\n"); + LOG(logERROR, (mess)); + return FAIL; } fscanf(file, "%x", &value); fclose(file); - // enable support for ARDY signal on interface to FPGA - // needed to properly translate avalon_mm_waitrequest in the CTB firmware - // https://www.analog.com/media/en/dsp-documentation/processor-manuals/bf537_hwr_Rev3.2.pdf - // page 274 - value |= 0x3; - + value |= BFIN_AMC_ACCESS_EXTENSION_ENA_VAL; file = fopen(file_path, "w"); if (!file) { - LOG(logERROR, ("Failed to enable blackfin AMC access extension\n")); - return; + strcpy(mess, "Failed to enable blackfin AMC access extension. Could " + "not write EBIU_AMBCTL1\n"); + LOG(logERROR, (mess)); + return FAIL; } fprintf(file, "0x%x", value); fclose(file); + return OK; } /* initialization */ diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h index 683338861..103502f1f 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h @@ -7,11 +7,9 @@ #define MIN_REQRD_VRSN_T_RD_API 0x181130 #define REQRD_FRMWR_VRSN 0x230705 -#define NUM_HARDWARE_VERSIONS (1) -#define HARDWARE_VERSION_NUMBERS \ - { 0x3f } -#define HARDWARE_VERSION_NAMES \ - { "5.1" } +#define NUM_HARDWARE_VERSIONS (1) +#define HARDWARE_VERSION_NUMBERS {0x3f} +#define HARDWARE_VERSION_NAMES {"5.1"} #define LINKED_SERVER_NAME "ctbDetectorServer" diff --git a/slsDetectorServers/slsDetectorServer/include/blackfin.h b/slsDetectorServers/slsDetectorServer/include/blackfin.h index fef76ad48..672f58c57 100644 --- a/slsDetectorServers/slsDetectorServer/include/blackfin.h +++ b/slsDetectorServers/slsDetectorServer/include/blackfin.h @@ -5,6 +5,23 @@ #include #include +/** enable support for ARDY signal on interface to FPGA + * needed to properly translate avalon_mm_waitrequest in the CTB firmware + * https://www.analog.com/media/en/dsp-documentation/processor-manuals/bf537_hwr_Rev3.2.pdf + * page 274 + * */ +#define BFIN_EBIU_AMBCTL1_B2_ARDY_ENA_OFST (0) +#define BFIN_EBIU_AMBCTL1_B2_ARDY_ENA_MSK \ + (1 << BFIN_EBIU_AMBCTL1_B2_ARDY_ENA_OFST) +#define BFIN_EBIU_AMBCTL1_B2_ARDY_POL_OFST (1) +#define BFIN_EBIU_AMBCTL1_B2_ARDY_POL_MSK \ + (1 << BFIN_EBIU_AMBCTL1_B2_ARDY_POL_OFST) + +#define BFIN_AMC_ACCESS_EXTENSION_ENA_VAL \ + (BFIN_EBIU_AMBCTL1_B2_ARDY_ENA_MSK | BFIN_EBIU_AMBCTL1_B2_ARDY_POL_MSK) +#define BFIN_AMC_ACCESS_EXTENSION_FNAME \ + "/sys/kernel/debug/blackfin/ebiu_amc/EBIU_AMBCTL1" + /** I2C defines */ #define I2C_CLOCK_MHZ (131.25) diff --git a/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h b/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h index 80bad5898..8570456f3 100644 --- a/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h +++ b/slsDetectorServers/slsDetectorServer/include/slsDetectorFunctionList.h @@ -113,6 +113,10 @@ void setModuleId(int modid); u_int64_t getDetectorMAC(); u_int32_t getDetectorIP(); +#if defined(CHIPTESTBOARDD) +int enableBlackfinAMCExternalAccessExtension(char *mess); +#endif + // initialization void initControlServer(); void initStopServer(); @@ -135,7 +139,6 @@ void setupDetector(); #if defined(CHIPTESTBOARDD) int updateDatabytesandAllocateRAM(); void updateDataBytes(); -void enableBlackfinAMCExternalAccessExtension(); #endif #if !defined(CHIPTESTBOARDD) && !defined(XILINX_CHIPTESTBOARDD) diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index b9a1d824d..5b991f4f1 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -3,10 +3,10 @@ /** API versions */ #define APILIB "developer 0x241122" #define APIRECEIVER "developer 0x241122" -#define APICTB "developer 0x250310" #define APIGOTTHARD2 "developer 0x250310" #define APIMOENCH "developer 0x250310" #define APIEIGER "developer 0x250310" #define APIXILINXCTB "developer 0x250311" #define APIJUNGFRAU "developer 0x250318" #define APIMYTHEN3 "developer 0x250409" +#define APICTB "developer 0x250519" From d7c012d3066d0d7c74be453bd40be8c30e7242c4 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 19 May 2025 13:20:03 +0200 Subject: [PATCH 083/103] formatting --- .../ctbDetectorServer/slsDetectorServer_defs.h | 8 +++++--- slsReceiverSoftware/src/DataProcessor.h | 2 +- slsReceiverSoftware/src/GeneralData.h | 4 ++-- .../tests/test-ArrangeDataBasedOnBitList.cpp | 4 ++-- slsSupportLib/include/sls/versionAPI.h | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h index 103502f1f..683338861 100644 --- a/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h +++ b/slsDetectorServers/ctbDetectorServer/slsDetectorServer_defs.h @@ -7,9 +7,11 @@ #define MIN_REQRD_VRSN_T_RD_API 0x181130 #define REQRD_FRMWR_VRSN 0x230705 -#define NUM_HARDWARE_VERSIONS (1) -#define HARDWARE_VERSION_NUMBERS {0x3f} -#define HARDWARE_VERSION_NAMES {"5.1"} +#define NUM_HARDWARE_VERSIONS (1) +#define HARDWARE_VERSION_NUMBERS \ + { 0x3f } +#define HARDWARE_VERSION_NAMES \ + { "5.1" } #define LINKED_SERVER_NAME "ctbDetectorServer" diff --git a/slsReceiverSoftware/src/DataProcessor.h b/slsReceiverSoftware/src/DataProcessor.h index e21d73c90..2f1c8f4ba 100644 --- a/slsReceiverSoftware/src/DataProcessor.h +++ b/slsReceiverSoftware/src/DataProcessor.h @@ -168,7 +168,7 @@ class DataProcessor : private virtual slsDetectorDefs, public ThreadObject { uint32_t streamingTimerInMs; uint32_t streamingStartFnum; uint32_t currentFreqCount{0}; - struct timespec timerbegin{}; + struct timespec timerbegin {}; bool framePadding; std::atomic startedFlag{false}; std::atomic firstIndex{0}; diff --git a/slsReceiverSoftware/src/GeneralData.h b/slsReceiverSoftware/src/GeneralData.h index 7147d0e2c..34a329542 100644 --- a/slsReceiverSoftware/src/GeneralData.h +++ b/slsReceiverSoftware/src/GeneralData.h @@ -63,8 +63,8 @@ class GeneralData { slsDetectorDefs::frameDiscardPolicy frameDiscardMode{ slsDetectorDefs::NO_DISCARD}; - GeneralData() {}; - virtual ~GeneralData() {}; + GeneralData(){}; + virtual ~GeneralData(){}; // Returns the pixel depth in byte, 4 bits being 0.5 byte float GetPixelDepth() { return float(dynamicRange) / 8; } diff --git a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp index 7ad93b908..e8b586184 100644 --- a/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp +++ b/slsReceiverSoftware/tests/test-ArrangeDataBasedOnBitList.cpp @@ -47,8 +47,8 @@ class GeneralDataTest : public GeneralData { // dummy DataProcessor class for testing class DataProcessorTest : public DataProcessor { public: - DataProcessorTest() : DataProcessor(0) {}; - ~DataProcessorTest() {}; + DataProcessorTest() : DataProcessor(0){}; + ~DataProcessorTest(){}; void ArrangeDbitData(size_t &size, char *data) { DataProcessor::ArrangeDbitData(size, data); } diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index 5b991f4f1..b6f8df9b3 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -9,4 +9,4 @@ #define APIXILINXCTB "developer 0x250311" #define APIJUNGFRAU "developer 0x250318" #define APIMYTHEN3 "developer 0x250409" -#define APICTB "developer 0x250519" +#define APICTB "developer 0x250519" From 30eab42294c08fd406dcab2f394a7b2c914d7e39 Mon Sep 17 00:00:00 2001 From: froejdh_e Date: Wed, 21 May 2025 15:46:04 +0200 Subject: [PATCH 084/103] rewrote end() for StaticVector --- slsSupportLib/include/sls/StaticVector.h | 6 +-- slsSupportLib/tests/test-StaticVector.cpp | 49 ++++++++++++++--------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/slsSupportLib/include/sls/StaticVector.h b/slsSupportLib/include/sls/StaticVector.h index 3c671f80b..cf72aa05d 100644 --- a/slsSupportLib/include/sls/StaticVector.h +++ b/slsSupportLib/include/sls/StaticVector.h @@ -113,10 +113,10 @@ template class StaticVector { // auto begin() noexcept -> decltype(data_.begin()) { return data_.begin(); // } const_iterator begin() const noexcept { return data_.begin(); } - iterator end() noexcept { return &data_[current_size]; } - const_iterator end() const noexcept { return &data_[current_size]; } + iterator end() noexcept { return data_.begin()+current_size; } + const_iterator end() const noexcept { return data_.begin()+current_size; } const_iterator cbegin() const noexcept { return data_.cbegin(); } - const_iterator cend() const noexcept { return &data_[current_size]; } + const_iterator cend() const noexcept { return data_.cbegin()+current_size; } void size_check(size_type s) const { if (s > Capacity) { diff --git a/slsSupportLib/tests/test-StaticVector.cpp b/slsSupportLib/tests/test-StaticVector.cpp index 6f9f5da50..2ff548fce 100644 --- a/slsSupportLib/tests/test-StaticVector.cpp +++ b/slsSupportLib/tests/test-StaticVector.cpp @@ -8,12 +8,15 @@ #include #include -namespace sls { + +using sls::StaticVector; TEST_CASE("StaticVector is a container") { - REQUIRE(is_container>::value == true); + REQUIRE(sls::is_container>::value == true); } + + TEST_CASE("Comparing StaticVector containers") { StaticVector a{0, 1, 2}; StaticVector b{0, 1, 2}; @@ -90,10 +93,17 @@ TEST_CASE("Copy construct from array") { REQUIRE(fcc == arr); } +TEST_CASE("Construct from a smaller StaticVector") { + StaticVector sv{1, 2, 3}; + StaticVector sv2{sv}; + REQUIRE(sv == sv2); +} + TEST_CASE("Free function and method gives the same iterators") { StaticVector fcc{1, 2, 3}; REQUIRE(std::begin(fcc) == fcc.begin()); } + SCENARIO("StaticVectors can be sized and resized", "[support]") { GIVEN("A default constructed container") { @@ -246,23 +256,23 @@ SCENARIO("Sorting, removing and other manipulation of a container", REQUIRE(a[3] == 90); } } - // WHEN("Sorting is done using free function for begin and end") { - // std::sort(begin(a), end(a)); - // THEN("it also works") { - // REQUIRE(a[0] == 12); - // REQUIRE(a[1] == 12); - // REQUIRE(a[2] == 14); - // REQUIRE(a[3] == 90); - // } - // } - // WHEN("Erasing elements of a certain value") { - // a.erase(std::remove(begin(a), end(a), 12)); - // THEN("all elements of that value are removed") { - // REQUIRE(a.size() == 2); - // REQUIRE(a[0] == 14); - // REQUIRE(a[1] == 90); - // } - // } + WHEN("Sorting is done using free function for begin and end") { + std::sort(std::begin(a), std::end(a)); + THEN("it also works") { + REQUIRE(a[0] == 12); + REQUIRE(a[1] == 12); + REQUIRE(a[2] == 14); + REQUIRE(a[3] == 90); + } + } + WHEN("Erasing elements of a certain value") { + a.erase(std::remove(std::begin(a), std::end(a), 12)); + THEN("all elements of that value are removed") { + REQUIRE(a.size() == 2); + REQUIRE(a[0] == 14); + REQUIRE(a[1] == 90); + } + } } } @@ -335,4 +345,3 @@ TEST_CASE("StaticVector stream") { REQUIRE(oss.str() == "[33, 85667, 2]"); } -} // namespace sls From 995d3e00348ef8afb54bbfe6ecb85a029399dae0 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 21 May 2025 14:30:10 +0200 Subject: [PATCH 085/103] rearranged receiver topics, differentiated btween receiver variants and added info about slsFrameSynchronizer --- docs/src/index.rst | 3 +- docs/src/receivers.rst | 22 +++++----- docs/src/slsreceiver.rst | 88 ++++++++++++++++++++++++++++++++++++---- docs/src/udpconfig.rst | 2 +- 4 files changed, 94 insertions(+), 21 deletions(-) diff --git a/docs/src/index.rst b/docs/src/index.rst index 206b08eef..6c3165a5a 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -81,8 +81,9 @@ Welcome to slsDetectorPackage's documentation! :caption: Receiver :maxdepth: 2 - receivers slsreceiver + receivers + .. toctree:: :caption: Receiver Files diff --git a/docs/src/receivers.rst b/docs/src/receivers.rst index 29937cf84..af919faa2 100644 --- a/docs/src/receivers.rst +++ b/docs/src/receivers.rst @@ -1,25 +1,25 @@ -Receivers +Custom Receiver ================= -Receiver processes can be run on same or different machines as the client, receives the data from the detector (via UDP packets). -When using the slsReceiver/ slsMultiReceiver, they can be further configured by the client control software (via TCP/IP) to set file name, file path, progress of acquisition etc. +The receiver essentially listens to UDP data packats sent out by the detector. + +To know more about detector receiver setup in the config file, please check out :ref:`the detector-receiver UDP configuration in the config file` and the :ref:`detector udp format`. -To know more about detector receiver configuration, please check out :ref:`detector udp header and udp commands in the config file ` +| Please note the following when using a custom receiver: -Custom Receiver ----------------- +* **udp_dstmac** must be configured in the config file. This parameter is not required when using an in-built receiver. -| When using custom receiver with our package, ensure that **udp_dstmac** is also configured in the config file. This parameter is not required when using slsReceiver. +* Cannot use "auto" for **udp_dstip**. -| Cannot use "auto" for **udp_dstip**. +* No **rx_** commands in the config file. These commands are for configuring the slsReceiver. -| Also ensure that there are no **rx_** commands in the config file. These commands are for configuring the slsReceiver. + + +The main difference is the lack of **rx_** commands or file commands (eg. **f**write, **f**path) and the **udp_dstmac** is required in config file. Example of a custom receiver config file -* The main difference is the lack of **rx_** commands or file commands (eg. fwrite, fpath) and the udp_dstmac is required in config file. - .. code-block:: bash # detector hostname diff --git a/docs/src/slsreceiver.rst b/docs/src/slsreceiver.rst index 0413eb10a..847f12a05 100644 --- a/docs/src/slsreceiver.rst +++ b/docs/src/slsreceiver.rst @@ -1,8 +1,44 @@ -slsReceiver/ slsMultiReceiver +In-built Receiver ================================ -| One has to start the slsReceiver before loading config file or using any receiver commands (prefix: **rx_** ) + +The receiver essentially listens to UDP data packets sent out by the detector. It's main features are: + +- **Listening**: Receives UDP data from the detector. +- **Writing to File**: Optionally writes received data to disk. +- **Streaming via ZMQ**: Optionally streams out the data using ZeroMQ. + +Each of these operations runs asynchronously and in parallel for each UDP port. + + +.. note :: + + * Can be run on the same or different machine as the client. + * Can be configured by the client. (set file name/ discard policy, get progress etc.) + * Has to be started before the client runs any receiver specific command. + + +Receiver Variants +----------------- +There are three main receiver types. How to start them is described :ref:`below`. + + ++----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ +| Receiver Type | Modules Supported| Internal Architecture | ZMQ Streaming | ZMQ Synchronization| Image Reconstruction| ++======================+==================+=========================================+====================+====================+=====================+ +| slsReceiver | 1 | Threads per port | Disabled by default| No | No | ++----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ +| slsMultiReceiver | Multiple | Multiple child processes of slsReceiver | Disabled by default| No | No | ++----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ +| slsFrameSynchronizer | Multiple | Multi-threading of slsReceiver | Enabled | Yes, across ports | No | ++----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ + + +.. _Starting up the Receiver: + +Starting up the Receiver +------------------------- For a Single Module .. code-block:: bash @@ -18,25 +54,35 @@ For Multiple Modules # each receiver (for each module) requires a unique tcp port (if all on same machine) - # using slsReceiver in multiple consoles + # using slsReceiver in multiple consoles (one for each module) slsReceiver slsReceiver -t1955 - # slsMultiReceiver [starting port] [number of receivers] + # slsMultiReceiver [starting port] [number of receivers/modules] slsMultiReceiver 2012 2 + slsFrameSynchronizer 2012 2 - # slsMultiReceiver [starting port] [number of receivers] [print each frame header for debugging] + + # slsMultiReceiver [starting port] [number of receivers/modules] [print for debugging] slsMultiReceiver 2012 2 1 + slsFrameSynchronizer 2012 2 1 + Client Commands ----------------- -| One can remove **udp_dstmac** from the config file, as the slsReceiver fetches this from the **udp_ip**. +* Client commands to the receiver begin with **rx_** or **f_** (file commands). -| One can use "auto" for **udp_dstip** if one wants to use default ip of **rx_hostname**. +* **rx_hostname** has to be the first command to the receiver so the client knows which reciever process to communicate with. -| The first command to the receiver (**rx_** commands) should be **rx_hostname**. The following are the different ways to establish contact. +* Can use 'auto' for **udp_dstip** if using 1GbE interface or the :ref:`virtual simulators`. + + +To know more about detector receiver setup in the config file, please check out :ref:`the detector-receiver UDP configuration in the config file` and the :ref:`detector udp format`. + + +The following are the different ways to establish contact using **rx_hostname** command. .. code-block:: bash @@ -91,6 +137,32 @@ Client Commands sls_detector_get -h rx_framescaught +Example of a config file using in-built receiver + +.. code-block:: bash + + # detector hostname + hostname bchip052+bchip053+ + + # udp destination port (receiver) + # sets increasing destination udp ports starting at 50004 + udp_dstport 50004 + + # udp destination ip (receiver) + 0:udp_dstip 10.0.1.100 + 1:udp_dstip 10.0.2.100 + + # udp source ip (same subnet as udp_dstip) + 0:udp_srcip 10.0.1.184 + 1:udp_srcip 10.0.2.184 + + # udp destination mac - not required (picked up from udp_dstip) + #udp_dstmac 22:47:d5:48:ad:ef + + # connects to receivers at increasing tcp port starting at 1954 + rx_hostname mpc3434 + # same as rx_hostname mpc3434:1954+mpc3434:1955+ + Performance diff --git a/docs/src/udpconfig.rst b/docs/src/udpconfig.rst index f3e56d82a..847b11899 100644 --- a/docs/src/udpconfig.rst +++ b/docs/src/udpconfig.rst @@ -1,4 +1,4 @@ -.. _detector udp header: +.. _detector udp header config: Config file From a4642625584b10b06531a3f0f0862b3e64255163 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 21 May 2025 15:46:49 +0200 Subject: [PATCH 086/103] typo --- docs/src/receivers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/receivers.rst b/docs/src/receivers.rst index af919faa2..31ff8f734 100644 --- a/docs/src/receivers.rst +++ b/docs/src/receivers.rst @@ -1,7 +1,7 @@ Custom Receiver ================= -The receiver essentially listens to UDP data packats sent out by the detector. +The receiver essentially listens to UDP data packets sent out by the detector. To know more about detector receiver setup in the config file, please check out :ref:`the detector-receiver UDP configuration in the config file` and the :ref:`detector udp format`. From 58245a62a4a7b1ffab117d7001af4e5a97443355 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 21 May 2025 17:05:38 +0200 Subject: [PATCH 087/103] minor aesthetics --- docs/src/serverupgrade.rst | 1 + docs/src/slsreceiver.rst | 23 ++++++++++++++--------- docs/src/virtualserver.rst | 1 + 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/src/serverupgrade.rst b/docs/src/serverupgrade.rst index c6fe1675a..a215da8ae 100644 --- a/docs/src/serverupgrade.rst +++ b/docs/src/serverupgrade.rst @@ -1,4 +1,5 @@ .. _Detector Server Upgrade: + Upgrade ======== diff --git a/docs/src/slsreceiver.rst b/docs/src/slsreceiver.rst index 847f12a05..8e96ca8aa 100644 --- a/docs/src/slsreceiver.rst +++ b/docs/src/slsreceiver.rst @@ -23,16 +23,21 @@ Receiver Variants ----------------- There are three main receiver types. How to start them is described :ref:`below`. ++----------------------+--------------------+-----------------------------------------+--------------------------------+ +| Receiver Type | slsReceiver | slsMultiReceiver |slsFrameSynchronizer | ++======================+====================+=========================================+================================+ +| Modules Supported | 1 | Multiple | Multiple | ++----------------------+--------------------+-----------------------------------------+--------------------------------+ +| Internal Architecture| Threads per porttt | Multiple child processes of slsReceiver | Multi-threading of slsReceiver | ++----------------------+--------------------+-----------------------------------------+--------------------------------+ +| ZMQ Streaming | Disabled by default| Disabled by default | Enabled, not optional | ++----------------------+--------------------+-----------------------------------------+--------------------------------+ +| ZMQ Synchronization | No | No | Yes, across ports | ++----------------------+--------------------+-----------------------------------------+--------------------------------+ +| Image Reconstruction | No | No | No | ++----------------------+--------------------+-----------------------------------------+--------------------------------+ + -+----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ -| Receiver Type | Modules Supported| Internal Architecture | ZMQ Streaming | ZMQ Synchronization| Image Reconstruction| -+======================+==================+=========================================+====================+====================+=====================+ -| slsReceiver | 1 | Threads per port | Disabled by default| No | No | -+----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ -| slsMultiReceiver | Multiple | Multiple child processes of slsReceiver | Disabled by default| No | No | -+----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ -| slsFrameSynchronizer | Multiple | Multi-threading of slsReceiver | Enabled | Yes, across ports | No | -+----------------------+------------------+-----------------------------------------+--------------------+--------------------+---------------------+ .. _Starting up the Receiver: diff --git a/docs/src/virtualserver.rst b/docs/src/virtualserver.rst index b96f285f8..239e51490 100644 --- a/docs/src/virtualserver.rst +++ b/docs/src/virtualserver.rst @@ -1,4 +1,5 @@ .. _Virtual Detector Servers: + Simulators =========== From 1c7bc61531c59d9a5e53e9e3e67ac4a7d171fa80 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 21 May 2025 17:27:31 +0200 Subject: [PATCH 088/103] minor --- docs/src/slsreceiver.rst | 53 +++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/docs/src/slsreceiver.rst b/docs/src/slsreceiver.rst index 8e96ca8aa..932c25a05 100644 --- a/docs/src/slsreceiver.rst +++ b/docs/src/slsreceiver.rst @@ -46,12 +46,10 @@ Starting up the Receiver ------------------------- For a Single Module .. code-block:: bash + + slsReceiver # default port 1954 - # default port 1954 - slsReceiver - - # custom port 2012 - slsReceiver -t2012 + slsReceiver -t2012 # custom port 2012 For Multiple Modules @@ -59,27 +57,24 @@ For Multiple Modules # each receiver (for each module) requires a unique tcp port (if all on same machine) - # using slsReceiver in multiple consoles (one for each module) + # option 1 (one for each module) slsReceiver slsReceiver -t1955 - # slsMultiReceiver [starting port] [number of receivers/modules] + # option 2 slsMultiReceiver 2012 2 + + # option 3 slsFrameSynchronizer 2012 2 - # slsMultiReceiver [starting port] [number of receivers/modules] [print for debugging] - slsMultiReceiver 2012 2 1 - slsFrameSynchronizer 2012 2 1 - - Client Commands ----------------- * Client commands to the receiver begin with **rx_** or **f_** (file commands). -* **rx_hostname** has to be the first command to the receiver so the client knows which reciever process to communicate with. +* **rx_hostname** has to be the first command to the receiver so the client knows which receiver process to communicate with. * Can use 'auto' for **udp_dstip** if using 1GbE interface or the :ref:`virtual simulators`. @@ -91,35 +86,37 @@ The following are the different ways to establish contact using **rx_hostname** .. code-block:: bash - # default receiver tcp port (1954) + # ---single module--- + + # default receiver port at 1954 + rx_hostname xxx + + # custom receiver port + rx_hostname xxx:1957 # option 1 + + rx_tcpport 1957 # option 2 rx_hostname xxx - # custom receiver port - rx_hostname xxx:1957 - # custom receiver port - rx_tcpport 1954 - rx_hostname xxx + # ---multi module--- - # multi modules with custom ports - rx_hostname xxx:1955+xxx:1956+ - - - # multi modules using increasing tcp ports when using multi detector command + # using increasing tcp ports rx_tcpport 1955 rx_hostname xxx - # or specify multi modules with custom ports on same rxr pc - 0:rx_tcpport 1954 + # custom ports + rx_hostname xxx:1955+xxx:1958+ # option 1 + + 0:rx_tcpport 1954 # option 2 1:rx_tcpport 1955 2:rx_tcpport 1956 rx_hostname xxx - # multi modules with custom ports on different rxr pc + # custom ports on different receiver machines 0:rx_tcpport 1954 0:rx_hostname xxx 1:rx_tcpport 1955 - 1:rx_hostname yyy + 1:rx_hostname yyyrxr | Example commands: From ceecb0ca27350a0f4e53d199a6aac0e44d586ce2 Mon Sep 17 00:00:00 2001 From: froejdh_e Date: Thu, 22 May 2025 10:20:16 +0200 Subject: [PATCH 089/103] added extra fs link and fixed execute_program warning --- CMakeLists.txt | 9 ++++---- cmake/FindClangFormat.cmake | 4 +++- cmake/helpers.cmake | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 cmake/helpers.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 212281f68..3b290aa5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ if (${CMAKE_VERSION} VERSION_GREATER "3.24") endif() include(cmake/project_version.cmake) include(cmake/SlsAddFlag.cmake) +include(cmake/helpers.cmake) @@ -193,11 +194,9 @@ find_package(ClangFormat) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "No build type selected, default to Release") - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) -endif() - +default_build_type("Release") +set_std_fs_lib() +message(STATUS "Extra linking to fs lib:${STD_FS_LIB}") #Enable LTO if available include(CheckIPOSupported) diff --git a/cmake/FindClangFormat.cmake b/cmake/FindClangFormat.cmake index 8e697896f..7b7fdd27f 100644 --- a/cmake/FindClangFormat.cmake +++ b/cmake/FindClangFormat.cmake @@ -25,7 +25,9 @@ mark_as_advanced( ClangFormat_BIN) if(ClangFormat_FOUND) - exec_program(${ClangFormat_BIN} ${CMAKE_CURRENT_SOURCE_DIR} ARGS --version OUTPUT_VARIABLE CLANG_VERSION_TEXT) + execute_process(COMMAND ${ClangFormat_BIN} --version + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE CLANG_VERSION_TEXT) string(REGEX MATCH "([0-9]+)\\.[0-9]+\\.[0-9]+" CLANG_VERSION ${CLANG_VERSION_TEXT}) if((${CLANG_VERSION} GREATER "9") OR (${CLANG_VERSION} EQUAL "9")) # A CMake script to find all source files and setup clang-format targets for them diff --git a/cmake/helpers.cmake b/cmake/helpers.cmake new file mode 100644 index 000000000..2ddb9d057 --- /dev/null +++ b/cmake/helpers.cmake @@ -0,0 +1,46 @@ +function(default_build_type val) +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE ${val} CACHE STRING "Build type (default ${val})" FORCE) +endif() +endfunction() + +function(set_std_fs_lib) +# from pybind11 +# Check if we need to add -lstdc++fs or -lc++fs or nothing +if(DEFINED CMAKE_CXX_STANDARD AND CMAKE_CXX_STANDARD LESS 17) + set(STD_FS_NO_LIB_NEEDED TRUE) +elseif(MSVC) + set(STD_FS_NO_LIB_NEEDED TRUE) +else() + file( + WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.cpp + "#include \nint main(int argc, char ** argv) {\n std::filesystem::path p(argv[0]);\n return p.string().length();\n}" + ) + try_compile( + STD_FS_NO_LIB_NEEDED ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp + COMPILE_DEFINITIONS -std=c++17) + try_compile( + STD_FS_NEEDS_STDCXXFS ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp + COMPILE_DEFINITIONS -std=c++17 + LINK_LIBRARIES stdc++fs) + try_compile( + STD_FS_NEEDS_CXXFS ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp + COMPILE_DEFINITIONS -std=c++17 + LINK_LIBRARIES c++fs) +endif() + +if(${STD_FS_NEEDS_STDCXXFS}) + set(STD_FS_LIB stdc++fs PARENT_SCOPE) +elseif(${STD_FS_NEEDS_CXXFS}) + set(STD_FS_LIB c++fs PARENT_SCOPE) +elseif(${STD_FS_NO_LIB_NEEDED}) + set(STD_FS_LIB "" PARENT_SCOPE) +else() + message(WARNING "Unknown C++17 compiler - not passing -lstdc++fs") + set(STD_FS_LIB "") +endif() +endfunction() \ No newline at end of file From d8ce5eabb86ec120d2a746724965cf5d7ba177a2 Mon Sep 17 00:00:00 2001 From: froejdh_e Date: Thu, 22 May 2025 10:39:32 +0200 Subject: [PATCH 090/103] and now with link --- slsSupportLib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/slsSupportLib/CMakeLists.txt b/slsSupportLib/CMakeLists.txt index 02bf2e610..ee847a369 100755 --- a/slsSupportLib/CMakeLists.txt +++ b/slsSupportLib/CMakeLists.txt @@ -88,6 +88,7 @@ message(STATUS "RAPID: ${SLS_INTERNAL_RAPIDJSON_DIR}") target_link_libraries(slsSupportObject PUBLIC slsProjectOptions + ${STD_FS_LIB} # from helpers.cmake PRIVATE slsProjectWarnings From b36a5b993352082566d473a5bc6d1a5b3096ff4f Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Thu, 22 May 2025 13:50:26 +0200 Subject: [PATCH 091/103] updating pmods --- psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants b/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants index c20d6f7e1..4a2904a5d 100644 --- a/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants +++ b/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants @@ -13,5 +13,5 @@ slsDetectorPackage/8.0.2_rh7 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/8.0.2_rh8 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/9.0.0_rh8 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/9.1.0_rh8 stable cmake/3.15.5 Qt/5.12.10 - +slsDetectorPackage/9.1.1_rh8 stable cmake/3.15.5 Qt/5.12.10 From 6d2f34ef1d80173fa3b118570d6ccdf3e2b7fd8e Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Fri, 23 May 2025 11:41:56 +0200 Subject: [PATCH 092/103] adresses review comments --- slsDetectorServers/compileAllServers.sh | 16 ++++++++++------ slsDetectorServers/compileEigerServer.sh | 15 +++++++++------ slsDetectorServers/ctbDetectorServer/Makefile | 3 +-- slsSupportLib/include/sls/Version.h | 2 +- slsSupportLib/src/Version.cpp | 2 +- updateAPIVersion.py | 2 +- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/slsDetectorServers/compileAllServers.sh b/slsDetectorServers/compileAllServers.sh index 5d184c8da..284f6dfa6 100755 --- a/slsDetectorServers/compileAllServers.sh +++ b/slsDetectorServers/compileAllServers.sh @@ -10,8 +10,11 @@ det_list=("ctbDetectorServer ) usage="\nUsage: compileAllServers.sh [server|all(opt)] [update_api(opt)]. \n\tNo arguments mean all servers with 'developer' branch. \n\tupdate_api if true updates the api to version in VERSION file" -update_api=false -target=clean +update_api=true +target=version + + + # arguments if [ $# -eq 0 ]; then # no argument, all servers @@ -37,9 +40,8 @@ elif [ $# -eq 1 ] || [ $# -eq 2 ]; then if [ $# -eq 2 ]; then update_api=$2 - if $update_api ; then - target=version - echo "updating api to $(cat ../VERSION)" + if not $update_api ; then + target=clean fi fi else @@ -51,6 +53,9 @@ declare -a deterror=("OK" "OK" "OK" "OK" "OK" "OK") echo -e "list is ${det[@]}" +if $update_api; then + echo "updating api to $(cat ../VERSION)" +fi # compile each server idet=0 for i in ${det[@]} @@ -67,7 +72,6 @@ do fi mv bin/$dir bin/$file git add -f bin/$file - cp bin/$file /tftpboot/ cd .. echo -e "\n\n" ((++idet)) diff --git a/slsDetectorServers/compileEigerServer.sh b/slsDetectorServers/compileEigerServer.sh index 37aefee6a..f5873dff8 100755 --- a/slsDetectorServers/compileEigerServer.sh +++ b/slsDetectorServers/compileEigerServer.sh @@ -6,21 +6,25 @@ file="${dir}_developer" usage="\nUsage: compileAllServers.sh [update_api(opt)]. \n\t update_api if true updates the api to version in VERSION file" -update_api=false -target=clean +update_api=true +target=version # arguments if [ $# -eq 1 ]; then update_api=$1 - if $update_api ; then - target=version - echo "updating api to $(cat ../VERSION)" + if not $update_api ; then + target=clean + fi elif [ ! $# -eq 0 ]; then echo -e "Only one optional argument allowed for update_api." return -1 fi +if $update_api; then + echo "updating api to $(cat ../VERSION)" +fi + echo -e "Compiling $dir [$file]" cd $dir if make $target; then @@ -31,7 +35,6 @@ fi mv bin/$dir bin/$file git add -f bin/$file -cp bin/$file /tftpboot/ cd .. echo -e "\n\n" printf "Result:\t\t= %s\n" "${deterror}" diff --git a/slsDetectorServers/ctbDetectorServer/Makefile b/slsDetectorServers/ctbDetectorServer/Makefile index 22547fdc5..0b56dd825 100755 --- a/slsDetectorServers/ctbDetectorServer/Makefile +++ b/slsDetectorServers/ctbDetectorServer/Makefile @@ -33,7 +33,6 @@ versioning: $(PROGS): $(OBJS) # echo $(OBJS) - cd $(current_dir) mkdir -p $(DESTDIR) $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) mv $(PROGS) $(DESTDIR) @@ -41,7 +40,7 @@ $(PROGS): $(OBJS) rm $(main_src)*.o $(md5_dir)*.o clean: - cd $(current_dir) && rm -rf $(DESTDIR)/$(PROGS) *.o *.gdb $(main_src)*.o $(md5_dir)*.o + rm -rf $(DESTDIR)/$(PROGS) *.o *.gdb $(main_src)*.o $(md5_dir)*.o diff --git a/slsSupportLib/include/sls/Version.h b/slsSupportLib/include/sls/Version.h index e4ec31631..6e97873e4 100644 --- a/slsSupportLib/include/sls/Version.h +++ b/slsSupportLib/include/sls/Version.h @@ -11,7 +11,7 @@ class Version { private: std::string version_; std::string date_; - inline static const std::string defaultBranch_[] = {"developer", "0.0.0"}; + inline static const std::string defaultVersion_[] = {"developer", "0.0.0"}; public: explicit Version(const std::string &s); diff --git a/slsSupportLib/src/Version.cpp b/slsSupportLib/src/Version.cpp index 3ace46217..03aeb5339 100644 --- a/slsSupportLib/src/Version.cpp +++ b/slsSupportLib/src/Version.cpp @@ -22,7 +22,7 @@ Version::Version(const std::string &s) { bool Version::hasSemanticVersioning() const { - return (version_ != defaultBranch_[0]) && (version_ != defaultBranch_[1]); + return (version_ != defaultVersion_[0]) && (version_ != defaultVersion_[1]); } std::string Version::getVersion() const { return version_; } diff --git a/updateAPIVersion.py b/updateAPIVersion.py index 934df081d..4d0cf7ccf 100644 --- a/updateAPIVersion.py +++ b/updateAPIVersion.py @@ -20,7 +20,7 @@ VERSION_FILE = SCRIPT_DIR + "/VERSION" parser = argparse.ArgumentParser(description = 'updates API version') parser.add_argument('api_module_name', choices=["APILIB", "APIRECEIVER", "APICTB", "APIGOTTHARD2", "APIMOENCH", "APIEIGER", "APIXILINXCTB", "APIJUNGFRAU", "APIMYTHEN3"], help = 'module name to change api version options are: ["APILIB", "APIRECEIVER", "APICTB", "APIGOTTHARD2", "APIMOENCH", "APIEIGER", "APIXILINXCTB", "APIJUNGFRAU", "APIMYTHEN3"]') -parser.add_argument('api_dir', help = 'Relative or absolut path to the module code') +parser.add_argument('api_dir', help = 'Relative or absolute path to the module code') def update_api_file(new_api : str, api_module_name : str, api_file_name : str): From feb1b0868e4b1af1d0cfc59537634a3b9464a805 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Mon, 26 May 2025 09:13:45 +0200 Subject: [PATCH 093/103] dummy commit for versionAPI --- slsSupportLib/include/sls/versionAPI.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index fb331c3cf..39bcc812e 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -6,7 +6,7 @@ #define APICTB "0.0.0 0x250515" #define APIGOTTHARD2 "developer 0x250310" #define APIMOENCH "developer 0x250310" -#define APIEIGER "developer 0x250310" +#define APIEIGER "0.0.0 0x250523" #define APIXILINXCTB "developer 0x250311" #define APIJUNGFRAU "developer 0x250318" #define APIMYTHEN3 "developer 0x250409" From 3ac7b579a0b3ecbca31fc7df5ed2dc7912ca00e2 Mon Sep 17 00:00:00 2001 From: mazzol_a Date: Mon, 26 May 2025 11:01:49 +0200 Subject: [PATCH 094/103] formatted and updated versionAPI.h --- slsSupportLib/include/sls/versionAPI.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index 39bcc812e..d88a71ba8 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -1,12 +1,12 @@ // SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package /** API versions */ -#define APILIB "0.0.0 0x250514" -#define APIRECEIVER "0.0.0 0x250515" -#define APICTB "0.0.0 0x250515" -#define APIGOTTHARD2 "developer 0x250310" -#define APIMOENCH "developer 0x250310" -#define APIEIGER "0.0.0 0x250523" -#define APIXILINXCTB "developer 0x250311" -#define APIJUNGFRAU "developer 0x250318" -#define APIMYTHEN3 "developer 0x250409" +#define APILIB "0.0.0 0x250523" +#define APIRECEIVER "0.0.0 0x250523" +#define APICTB "0.0.0 0x250523" +#define APIGOTTHARD2 "0.0.0 0x250523" +#define APIMOENCH "0.0.0 0x250523" +#define APIEIGER "0.0.0 0x250523" +#define APIXILINXCTB "0.0.0 0x250523" +#define APIJUNGFRAU "0.0.0 0x250523" +#define APIMYTHEN3 "0.0.0 0x250523" From ed142aa34eeaf76b307a783c719b57cd10137b17 Mon Sep 17 00:00:00 2001 From: froejdh_e Date: Thu, 22 May 2025 09:51:01 +0200 Subject: [PATCH 095/103] added expat to host section --- conda-recipes/main-library/meta.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/conda-recipes/main-library/meta.yaml b/conda-recipes/main-library/meta.yaml index 8b11b4536..d0841d6f4 100755 --- a/conda-recipes/main-library/meta.yaml +++ b/conda-recipes/main-library/meta.yaml @@ -28,7 +28,8 @@ requirements: - libgl-devel # [linux] - libtiff - zlib - + - expat + run: - libstdcxx-ng - libgcc-ng @@ -57,6 +58,7 @@ outputs: - {{ compiler('c') }} - {{compiler('cxx')}} - {{ pin_subpackage('slsdetlib', exact=True) }} + run: - {{ pin_subpackage('slsdetlib', exact=True) }} From 50ab20317dba8a6c2de765d50a92fe739929ddc4 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Wed, 28 May 2025 10:22:40 +0200 Subject: [PATCH 096/103] updated documentation for pip installation as well --- docs/src/installation.rst | 74 ++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/docs/src/installation.rst b/docs/src/installation.rst index 2f1dca474..82128f9e5 100644 --- a/docs/src/installation.rst +++ b/docs/src/installation.rst @@ -6,18 +6,33 @@ Installation =============== -One can either install pre-built binaries using conda or build from source. +.. contents:: + :local: + :depth: 2 + :backlinks: top + + +Overview +-------------- + +The ``slsDetectorPackage`` provides core detector software implemented in C++, along with Python bindings packaged as the ``slsdet`` Python extension module. Choose the option that best fits your environment and use case. + +:ref:`conda pre-built binaries`: +Install pre-built binaries for the C++ client, receiver, GUI and the Python API (``slsdet``), simplifying setup across platforms. + +:ref:`pip`: +Install only the Python extension module, either by downloading the pre-built library from PyPI or by building the extension locally from source. Available only from v9.2.0 onwards. + +:ref:`build from source`: +Compile the entire package yourself, including both the C++ core and the Python bindings, for maximum control and customization. However, make sure that you have the :doc:`dependencies <../dependencies>` installed. If installing using conda, conda will manage the dependencies. Avoid installing packages with pip and conda simultaneously. + + -.. warning :: - - Before building from source make sure that you have the - :doc:`dependencies <../dependencies>` installed. If installing using conda, conda will - manage the dependencies. Avoid also installing packages with pip. +.. _conda pre-built binaries: - -Install binaries using conda ----------------------------------- +1. Install pre-built binaries using conda (Recommended) +-------------------------------------------------------- Conda is not only useful to manage python environments but can also be used as a user space package manager. Dates in the tag (for eg. 2020.07.23.dev0) @@ -63,12 +78,29 @@ We have four different packages available: conda search moenchzmq +.. _pip: + +2. Pip +------- +The Python extension module ``slsdet`` can be installed using pip. This is available from v9.2.0 onwards. + +.. code-block:: bash + + #Install the Python extension module from PyPI + pip install slsdet + + # or install the python extension locally from source + git clone https://github.com/slsdetectorgroup/slsDetectorPackage.git --branch 9.2.0 + cd slsDetectorPackage + pip install . -Build from source ----------------------- +.. _build from source: -1. Download Source Code from github +3. Build from source +------------------------- + +3.1. Download Source Code from github ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash @@ -83,12 +115,12 @@ Build from source -2. Build from Source +3.2. Build from Source ^^^^^^^^^^^^^^^^^^^^^^^^^^ One can either build using cmake or use the in-built cmk.sh script. -Build using CMake +3.2.1. Build using CMake ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash @@ -135,7 +167,7 @@ Example cmake options Comment For v7.x.x of slsDetectorPackage and older, refer :ref:`zeromq notes for cmake option to hint library location. ` -Build using in-built cmk.sh script +3.2.2. Build using in-built cmk.sh script ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -185,8 +217,8 @@ Build using in-built cmk.sh script For v7.x.x of slsDetectorPackage and older, refer :ref:`zeromq notes for cmk script option to hint library location. ` -Build on old distributions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +3.3. Build on old distributions using conda +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If your linux distribution doesn't come with a C++11 compiler (gcc>4.8) then it's possible to install a newer gcc using conda and build the slsDetectorPackage @@ -210,7 +242,7 @@ using this compiler -Build slsDetectorGui (Qt5) +3.4. Build slsDetectorGui (Qt5) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Using pre-built binary on conda @@ -271,7 +303,7 @@ Build slsDetectorGui (Qt5) -Build this documentation +3.5. Build this documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The documentation for the slsDetectorPackage is build using a combination @@ -294,8 +326,8 @@ is to use conda make rst # rst only, saves time in case the API did not change -Pybind and Zeromq -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +4. Pybind and Zeromq +------------------------- .. _pybind for different slsDetectorPackage versions: From e77fd8d85d937c8eefa4242e8a3c358c67c60cc4 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Fri, 30 May 2025 08:17:45 +0200 Subject: [PATCH 097/103] Dev/add numpy (#1227) * added numpy dependency * added build specifications for python version and platform --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8deb25dde..15833499c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,9 +11,13 @@ build-backend = "scikit_build_core.build" [project] name = "slsdet" dynamic = ["version"] +dependencies = [ + "numpy", +] [tool.cibuildwheel] before-all = "uname -a" +build = "cp{311,312,313}-manylinux_x86_64" [tool.scikit-build.build] verbose = true From c92830f854db01dd8b30df4bc627c772a7e7fe25 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 2 Jun 2025 15:16:39 +0200 Subject: [PATCH 098/103] updates files/variants for pmods for 9.2.0 (#1233) --- psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants b/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants index 4a2904a5d..f546606f3 100644 --- a/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants +++ b/psi-pmodules/DetectorSoftware/slsDetectorPackage/files/variants @@ -14,4 +14,4 @@ slsDetectorPackage/8.0.2_rh8 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/9.0.0_rh8 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/9.1.0_rh8 stable cmake/3.15.5 Qt/5.12.10 slsDetectorPackage/9.1.1_rh8 stable cmake/3.15.5 Qt/5.12.10 - +slsDetectorPackage/9.2.0_rh8 stable cmake/3.15.5 Qt/5.12.10 From f84454fbc11f830690c3124e92a38303b08f19d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Tue, 3 Jun 2025 08:36:29 +0200 Subject: [PATCH 099/103] tests for bool in ToString/StringTo (#1230) - Added tests for ToString/StringTo - Added overload for ToString of bool (previously went through int) --- slsDetectorSoftware/CMakeLists.txt | 1 - slsSupportLib/include/sls/ToString.h | 2 ++ slsSupportLib/src/ToString.cpp | 7 +++++++ slsSupportLib/tests/test-ToString.cpp | 11 +++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/slsDetectorSoftware/CMakeLists.txt b/slsDetectorSoftware/CMakeLists.txt index e61084330..6147546d6 100755 --- a/slsDetectorSoftware/CMakeLists.txt +++ b/slsDetectorSoftware/CMakeLists.txt @@ -100,7 +100,6 @@ if(SLS_USE_TEXTCLIENT) target_link_libraries(${val1} slsDetectorStatic pthread - rt ) SET_SOURCE_FILES_PROPERTIES( src/Caller.cpp PROPERTIES COMPILE_FLAGS "-Wno-unused-variable -Wno-unused-but-set-variable") diff --git a/slsSupportLib/include/sls/ToString.h b/slsSupportLib/include/sls/ToString.h index fc9a431bf..ea97a8436 100644 --- a/slsSupportLib/include/sls/ToString.h +++ b/slsSupportLib/include/sls/ToString.h @@ -47,6 +47,8 @@ std::string ToString(const defs::polarity s); std::string ToString(const defs::timingInfoDecoder s); std::string ToString(const defs::collectionMode s); +std::string ToString(bool value); + std::string ToString(const slsDetectorDefs::xy &coord); std::ostream &operator<<(std::ostream &os, const slsDetectorDefs::xy &coord); std::string ToString(const slsDetectorDefs::ROI &roi); diff --git a/slsSupportLib/src/ToString.cpp b/slsSupportLib/src/ToString.cpp index f815cdd27..1e109d9b1 100644 --- a/slsSupportLib/src/ToString.cpp +++ b/slsSupportLib/src/ToString.cpp @@ -5,6 +5,13 @@ namespace sls { + +std::string ToString(bool value) { + return value ? "1" : "0"; +} + + + std::string ToString(const slsDetectorDefs::xy &coord) { std::ostringstream oss; oss << '[' << coord.x << ", " << coord.y << ']'; diff --git a/slsSupportLib/tests/test-ToString.cpp b/slsSupportLib/tests/test-ToString.cpp index 636a91c28..4232169eb 100644 --- a/slsSupportLib/tests/test-ToString.cpp +++ b/slsSupportLib/tests/test-ToString.cpp @@ -16,6 +16,17 @@ namespace sls { using namespace sls::time; +TEST_CASE("Convert bool to string", "[support]") { + REQUIRE(ToString(true) == "1"); + REQUIRE(ToString(false) == "0"); +} + +TEST_CASE("Convert string to bool", "[support]") { + REQUIRE(StringTo("1") == true); + REQUIRE(StringTo("0") == false); +} + + TEST_CASE("Integer conversions", "[support]") { REQUIRE(ToString(0) == "0"); REQUIRE(ToString(1) == "1"); From bab6a5e9e1260438588dde8ccf02fb6bc9ccbedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Thu, 5 Jun 2025 14:01:08 +0200 Subject: [PATCH 100/103] added docs for SLSDETNAME (#1228) * added docs for SLSDETNAME * clarification on hostname * added examples on module index * fixes * fixed typo --- docs/CMakeLists.txt | 1 + docs/src/commandline.rst | 2 + docs/src/index.rst | 6 + docs/src/multidet.rst | 228 ++++++++++++++++++++++++++++++++++ docs/src/pygettingstarted.rst | 41 ++++++ 5 files changed, 278 insertions(+) create mode 100644 docs/src/multidet.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 19f09b884..df1b34e0f 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -42,6 +42,7 @@ set(SPHINX_SOURCE_FILES src/pyexamples.rst src/pyPatternGenerator.rst src/servers.rst + src/multidet.rst src/receiver_api.rst src/result.rst src/type_traits.rst diff --git a/docs/src/commandline.rst b/docs/src/commandline.rst index 25afce8ad..b65cf1cef 100644 --- a/docs/src/commandline.rst +++ b/docs/src/commandline.rst @@ -6,6 +6,8 @@ Usage The syntax is *'[detector index]-[module index]:[command]'*, where the indices are by default '0', when not specified. +.. _cl-module-index-label: + Module index ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Modules are indexed based on their order in the hostname command. They are used to configure a specific module within a detector and are followed by a ':' in syntax. diff --git a/docs/src/index.rst b/docs/src/index.rst index 6c3165a5a..558e59cc7 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -28,6 +28,12 @@ Welcome to slsDetectorPackage's documentation! receiver_api examples +.. toctree:: + :caption: how to + :maxdepth: 2 + + multidet + .. toctree:: :caption: Python API :maxdepth: 2 diff --git a/docs/src/multidet.rst b/docs/src/multidet.rst new file mode 100644 index 000000000..810e7852e --- /dev/null +++ b/docs/src/multidet.rst @@ -0,0 +1,228 @@ +Using multiple detectors +========================== + +The slsDetectorPackage supports using several detectors on the same computer. +This can either be two users, that need to use the same computer without interfering +with each other, or the same user that wants to use multiple detectors at the same time. +The detectors in turn can consist of multiple modules. For example, a 9M Jungfrau detector +consists of 18 modules which typically are addressed at once as a single detector. + +.. note :: + + To address a single module of a multi-module detector you can use the module index. + + - Command line: :ref:`cl-module-index-label` + - Python: :ref:`py-module-index-label` + + +Coming back to multiple detectors we have two tools to our disposal: + +#. Detector index +#. The SLSDETNAME environment variable + +They can be used together or separately depending on the use case. + +Detector index +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When configuring a detector you can specify a detector index. The default is 0. + +**Command line** + +.. code-block:: bash + + # Given that we have two detectors (my-det and my-det2) that we want to use, + # we can configure them with different indices. + + # Configure the first detector with index 0 + $ sls_detector_put hostname my-det + + # Set number of frames for detector 0 to 10 + $ sls_detector_put frames 10 + + + # + #Configure the second detector with index 1 (notice the 1- before hostname) + $ sls_detector_put 1-hostname my-det2 + + + # Further configuration + ... + + # Set number of frames for detector 1 to 19 + $ sls_detector_put 1-frames 19 + + # Note that if we call sls_detector_get without specifying the index, + # it will return the configuration of detector 0 + $ sls_detector_get frames + 10 + +The detector index is added to the name of the shared memory segment, so in this case +the shared memory segments would be: + +.. code-block:: bash + + + #First detector + /dev/shm/slsDetectorPackage_detector_0 + /dev/shm/slsDetectorPackage_detector_0_module_0 + + #Second detector + /dev/shm/slsDetectorPackage_detector_1 + /dev/shm/slsDetectorPackage_detector_1_module_0 + + +**Python** + +The main difference between the command line and the Python API is that you set the index +when you create the detector object and you don't have to repeat it for every call. + +The C++ API works int the same way. + +.. code-block:: python + + from slsdet import Detector + + + # The same can be achieved in Python by creating a detector object with an index. + # Again we have two detectors (my-det and my-det2) that we want to use: + + # Configure detector with index 0 + d = Detector() + + # If the detector has already been configured and has a shared memory + # segment, you can omit setting the hostname again + d.hostname = 'my-det' + + #Further configuration + ... + + # Configure a second detector with index 1 + d2 = Detector(1) + d2.hostname = 'my-det2' + + d.frames = 10 + d2.frames = 19 + + +$SLSDETNAME +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To avoid interfering with other users on shared PCs it is best to always set the SLSDETNAME environmental variable. +Imagining a fictive user: Anna, we can set SLSDETNAME from the shell before configuring the detector: + +**Command line** + +.. code-block:: bash + + # Set the SLSDETNAME variable + $ export SLSDETNAME=Anna + + # You can check that it is set + $ echo $SLSDETNAME + Anna + + # Now configures a detector with index 0 and prefixed with the name Anna + # /dev/shm/slsDetectorPackage_detector_0_Anna + $ sls_detector_put hostname my-det + + +.. tip :: + + Set SLSDETNAME in your .bashrc in order to not forget it when opening a new terminal. + + +**Python** + +With python the best way is to set the SLSDETNAME from the command line before starting the python interpreter. + +Bash: + +.. code-block:: bash + + $ export SLSDETNAME=Anna + +Python: + +.. code-block:: python + + from slsdet import Detector + + # Now configures a detector with index 0 and prefixed with the name Anna + # /dev/shm/slsDetectorPackage_detector_0_Anna + d = Detector() + d.hostname = 'my-det' + +You can also set SLSDETNAME from within the Python interpreter, but you have to be aware that it will only +affect the current process and not the whole shell session. + +.. code-block:: python + + import os + os.environ['SLSDETNAME'] = 'Anna' + + # You can check that it is set + print(os.environ['SLSDETNAME']) # Output: Anna + + #Now SLSDETNAME is set to Anna but as soon as you exit the python interpreter + # it will not be set anymore + +.. note :: + + Python has two ways of reading environment variables: `**os.environ**` as shown above which throws a + KeyError if the variable is not set and `os.getenv('SLSDETNAME')` which returns None if the variable is not set. + + For more details see the official python documentation on: https://docs.python.org/3/library/os.html#os.environ + + +Checking for other detectors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If using shared accounts on a shared computer (which you anyway should not do), it is good practice to check +if there are other detectors configured by other users before configuring your own detector. + +You can do this by listing the files in the shared memory directory `/dev/shm/` that start with `sls`. In this +example we can see that two single module detectors are configured one with index 0 and one with index 1. +SLSDETNAME is set to `Anna` so it makes sense to assume that she is the user that configured these detectors. + + +.. code-block:: bash + + # List the files in /dev/shm that starts with sls + $ ls /dev/shm/sls* + /dev/shm/slsDetectorPackage_detector_0_Anna + /dev/shm/slsDetectorPackage_detector_0_module_0_Anna + /dev/shm/slsDetectorPackage_detector_1_Anna + /dev/shm/slsDetectorPackage_detector_1_module_0_Anna + +We also provide a command: user, which gets information about the shared memory segment that +the client points to without doing any changes. + +.. code-block:: bash + + #in this case 3 simulated Mythen3 modules + $ sls_detector_get user + user + Hostname: localhost+localhost+localhost+ + Type: Mythen3 + PID: 1226078 + User: l_msdetect + Date: Mon Jun 2 05:46:20 PM CEST 2025 + + +Other considerations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The shared memory is not the only way to interfere with other users. You also need to make sure that you are not +using the same: + +* rx_tcpport +* Unique combination of udp_dstip and udp_dstport +* rx_zmqport +* zmqport + +.. attention :: + + The computer that you are using need to have enough resources to run multiple detectors at the same time. + This includes CPU and network bandwidth. Please coordinate with the other users! + diff --git a/docs/src/pygettingstarted.rst b/docs/src/pygettingstarted.rst index 547d597e0..6f234d5ae 100644 --- a/docs/src/pygettingstarted.rst +++ b/docs/src/pygettingstarted.rst @@ -123,6 +123,47 @@ in a large detector. # Set exposure time for module 1, 5 and 7 d.setExptime(0.1, [1,5,7]) + + +.. _py-module-index-label: + +---------------------------------- +Accessing individual modules +---------------------------------- + +Using the C++ like API you can access individual modules in a large detector +by passing in the module index as an argument to the function. + +:: + + # Read the UDP destination port for all modules + >>> d.getDestinationUDPPort() + [50001, 50002, 50003] + + + # Read it for module 0 and 1 + >>> d.getDestinationUDPPort([0, 1]) + [50001, 50002] + + >>> d.setDestinationUDPPort(50010, 1) + >>> d.getDestinationUDPPort() + [50001, 50010, 50003] + +From the more pythonic API there is no way to read from only one module but you can read +and then use list slicing to get the values for the modules you are interested in. + +:: + + >>> d.udp_dstport + [50001, 50010, 50003] + >>> d.udp_dstport[0] + 50001 + + #For some but not all properties you can also pass in a dictionary with module index as key + >>> ip = IpAddr('127.0.0.1') + >>> d.udp_dstip = {1:ip} + + -------------------- Finding functions -------------------- From ec67617e5cb2298eb445d7cb15e538e0392905dc Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Fri, 13 Jun 2025 14:20:01 +0200 Subject: [PATCH 101/103] Dev/update test framesynchronizer (#1221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * raise an exception if the pull socket python script had errors at startup (for eg if pyzmq was not installed) * minor changes that got lost in the merge of automate_version_part 2 PR --------- Co-authored-by: Erik Fröjdh --- tests/scripts/test_frame_synchronizer.py | 27 ++++++++++++------------ tests/scripts/utils_for_test.py | 22 +++++++++++++++++-- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/tests/scripts/test_frame_synchronizer.py b/tests/scripts/test_frame_synchronizer.py index 87c784cf7..8d2915f0a 100644 --- a/tests/scripts/test_frame_synchronizer.py +++ b/tests/scripts/test_frame_synchronizer.py @@ -20,6 +20,7 @@ from utils_for_test import ( cleanSharedmemory, startProcessInBackground, startProcessInBackgroundWithLogFile, + checkLogForErrors, startDetectorVirtualServer, loadConfig, ParseArguments @@ -34,6 +35,9 @@ def startFrameSynchronizerPullSocket(name, fp): fname = PULL_SOCKET_PREFIX_FNAME + name + '.txt' cmd = ['python', '-u', 'frameSynchronizerPullSocket.py'] startProcessInBackgroundWithLogFile(cmd, fp, fname) + time.sleep(1) + checkLogForErrors(fp, fname) + def startFrameSynchronizer(num_mods, fp): @@ -44,16 +48,14 @@ def startFrameSynchronizer(num_mods, fp): time.sleep(1) -def acquire(fp): +def acquire(fp, det): Log(LogLevel.INFO, 'Acquiring') Log(LogLevel.INFO, 'Acquiring', fp) - d = Detector() - d.acquire() + det.acquire() -def testFramesCaught(name, num_frames): - d = Detector() - fnum = d.rx_framescaught[0] +def testFramesCaught(name, det, num_frames): + fnum = det.rx_framescaught[0] if fnum != num_frames: raise RuntimeException(f"{name} caught only {fnum}. Expected {num_frames}") @@ -61,7 +63,7 @@ def testFramesCaught(name, num_frames): Log(LogLevel.INFOGREEN, f'Frames caught test passed for {name}', fp) -def testZmqHeadetTypeCount(name, num_mods, num_frames, fp): +def testZmqHeadetTypeCount(name, det, num_mods, num_frames, fp): Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}") Log(LogLevel.INFO, f"Testing Zmq Header type count for {name}", fp) @@ -88,8 +90,7 @@ def testZmqHeadetTypeCount(name, num_mods, num_frames, fp): continue # test if file contents matches expected counts - d = Detector() - num_ports_per_module = 1 if name == "gotthard2" else d.numinterfaces + num_ports_per_module = 1 if name == "gotthard2" else det.numinterfaces total_num_frame_parts = num_ports_per_module * num_mods * num_frames for htype, expected_count in [("header", num_mods), ("series_end", num_mods), ("module", total_num_frame_parts)]: if htype_counts[htype] != expected_count: @@ -111,10 +112,10 @@ def startTestsForAll(args, fp): startDetectorVirtualServer(server, args.num_mods, fp) startFrameSynchronizerPullSocket(server, fp) startFrameSynchronizer(args.num_mods, fp) - loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=args.num_mods, num_frames=args.num_frames) - acquire(fp) - testFramesCaught(server, args.num_frames) - testZmqHeadetTypeCount(server, args.num_mods, args.num_frames, fp) + d = loadConfig(name=server, rx_hostname=args.rx_hostname, settingsdir=args.settingspath, fp=fp, num_mods=args.num_mods, num_frames=args.num_frames) + acquire(fp, d) + testFramesCaught(server, d, args.num_frames) + testZmqHeadetTypeCount(server, d, args.num_mods, args.num_frames, fp) Log(LogLevel.INFO, '\n') except Exception as e: raise RuntimeException(f'Synchronizer Tests failed') from e diff --git a/tests/scripts/utils_for_test.py b/tests/scripts/utils_for_test.py index f2ca16cee..1d64129cf 100644 --- a/tests/scripts/utils_for_test.py +++ b/tests/scripts/utils_for_test.py @@ -104,7 +104,7 @@ def startProcessInBackground(cmd, fp): raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e -def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): +def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name: str): Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name) Log(LogLevel.INFOBLUE, 'Starting up ' + ' '.join(cmd) + '. Log: ' + log_file_name, fp) try: @@ -114,6 +114,22 @@ def startProcessInBackgroundWithLogFile(cmd, fp, log_file_name): raise RuntimeException(f'Failed to start {cmd}:{str(e)}') from e +def checkLogForErrors(fp, log_file_path: str): + try: + with open(log_file_path, 'r') as log_file: + for line in log_file: + if 'Error' in line: + Log(LogLevel.ERROR, f"Error found in log: {line.strip()}") + Log(LogLevel.ERROR, f"Error found in log: {line.strip()}", fp) + raise RuntimeException("Error found in log file") + except FileNotFoundError: + print(f"Log file not found: {log_file_path}") + raise + except Exception as e: + print(f"Exception while reading log: {e}") + raise + + def runProcessWithLogFile(name, cmd, fp, log_file_name): Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name) Log(LogLevel.INFOBLUE, 'Running ' + name + '. Log: ' + log_file_name, fp) @@ -140,7 +156,7 @@ def startDetectorVirtualServer(name :str, num_mods, fp): for i in range(num_mods): port_no = SERVER_START_PORTNO + (i * 2) cmd = [name + 'DetectorServer_virtual', '-p', str(port_no)] - startProcessInBackground(cmd, fp) + startProcessInBackgroundWithLogFile(cmd, fp, "/tmp/virtual_det_" + name + str(i) + ".txt") match name: case 'jungfrau': time.sleep(7) @@ -201,6 +217,8 @@ def loadConfig(name, rx_hostname, settingsdir, fp, num_mods = 1, num_frames = 1) d.frames = num_frames except Exception as e: raise RuntimeException(f'Could not load config for {name}. Error: {str(e)}') from e + + return d def ParseArguments(description, default_num_mods=1): From f4345a91a16d4074bc515496a57c79cb466bb113 Mon Sep 17 00:00:00 2001 From: froejdh_e Date: Fri, 13 Jun 2025 16:13:17 +0200 Subject: [PATCH 102/103] added workflow for python wheels --- .github/workflows/build_wheel.yml | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/build_wheel.yml diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml new file mode 100644 index 000000000..f131e7775 --- /dev/null +++ b/.github/workflows/build_wheel.yml @@ -0,0 +1,64 @@ +name: Build wheel + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + release: + types: + - published + + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest,] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + run: pipx run cibuildwheel==2.23.0 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) + # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 From 925176b6618368a2a291f1b2bdf7b3d81ff6b13d Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Mon, 16 Jun 2025 22:10:44 +0200 Subject: [PATCH 103/103] formatting --- slsSupportLib/include/sls/StaticVector.h | 8 +++++--- slsSupportLib/include/sls/versionAPI.h | 1 - slsSupportLib/src/ToString.cpp | 7 +------ slsSupportLib/tests/test-StaticVector.cpp | 4 ---- slsSupportLib/tests/test-ToString.cpp | 1 - 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/slsSupportLib/include/sls/StaticVector.h b/slsSupportLib/include/sls/StaticVector.h index cf72aa05d..931ea17fa 100644 --- a/slsSupportLib/include/sls/StaticVector.h +++ b/slsSupportLib/include/sls/StaticVector.h @@ -113,10 +113,12 @@ template class StaticVector { // auto begin() noexcept -> decltype(data_.begin()) { return data_.begin(); // } const_iterator begin() const noexcept { return data_.begin(); } - iterator end() noexcept { return data_.begin()+current_size; } - const_iterator end() const noexcept { return data_.begin()+current_size; } + iterator end() noexcept { return data_.begin() + current_size; } + const_iterator end() const noexcept { return data_.begin() + current_size; } const_iterator cbegin() const noexcept { return data_.cbegin(); } - const_iterator cend() const noexcept { return data_.cbegin()+current_size; } + const_iterator cend() const noexcept { + return data_.cbegin() + current_size; + } void size_check(size_type s) const { if (s > Capacity) { diff --git a/slsSupportLib/include/sls/versionAPI.h b/slsSupportLib/include/sls/versionAPI.h index 0453a3846..d88a71ba8 100644 --- a/slsSupportLib/include/sls/versionAPI.h +++ b/slsSupportLib/include/sls/versionAPI.h @@ -10,4 +10,3 @@ #define APIXILINXCTB "0.0.0 0x250523" #define APIJUNGFRAU "0.0.0 0x250523" #define APIMYTHEN3 "0.0.0 0x250523" - diff --git a/slsSupportLib/src/ToString.cpp b/slsSupportLib/src/ToString.cpp index 1e109d9b1..212514f6f 100644 --- a/slsSupportLib/src/ToString.cpp +++ b/slsSupportLib/src/ToString.cpp @@ -5,12 +5,7 @@ namespace sls { - -std::string ToString(bool value) { - return value ? "1" : "0"; -} - - +std::string ToString(bool value) { return value ? "1" : "0"; } std::string ToString(const slsDetectorDefs::xy &coord) { std::ostringstream oss; diff --git a/slsSupportLib/tests/test-StaticVector.cpp b/slsSupportLib/tests/test-StaticVector.cpp index 2ff548fce..fc40f1a0f 100644 --- a/slsSupportLib/tests/test-StaticVector.cpp +++ b/slsSupportLib/tests/test-StaticVector.cpp @@ -8,15 +8,12 @@ #include #include - using sls::StaticVector; TEST_CASE("StaticVector is a container") { REQUIRE(sls::is_container>::value == true); } - - TEST_CASE("Comparing StaticVector containers") { StaticVector a{0, 1, 2}; StaticVector b{0, 1, 2}; @@ -344,4 +341,3 @@ TEST_CASE("StaticVector stream") { oss << vec; REQUIRE(oss.str() == "[33, 85667, 2]"); } - diff --git a/slsSupportLib/tests/test-ToString.cpp b/slsSupportLib/tests/test-ToString.cpp index 4232169eb..76ee87245 100644 --- a/slsSupportLib/tests/test-ToString.cpp +++ b/slsSupportLib/tests/test-ToString.cpp @@ -26,7 +26,6 @@ TEST_CASE("Convert string to bool", "[support]") { REQUIRE(StringTo("0") == false); } - TEST_CASE("Integer conversions", "[support]") { REQUIRE(ToString(0) == "0"); REQUIRE(ToString(1) == "1");