// Copyright (2019-2023) Paul Scherrer Institute #include "JFJochBrokerParser.h" #include "JFJochBroker.h" #include "../common/NetworkAddressConvert.h" inline bool CHECK_ARRAY(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (!j[tag].is_array()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be array"); return true; } else return false; } inline bool CHECK_OBJECT(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (!j[tag].is_object()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be object"); return true; } else return false; } inline int64_t GET_I64(const nlohmann::json &j, const std::string& tag, int64_t def) { if (j.contains(tag)) { if (!j[tag].is_number_integer()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer"); try { return j[tag].get(); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } else return def; } inline bool GET_BOOL(const nlohmann::json &j, const std::string& tag, bool def) { if (j.contains(tag)) { if (!j[tag].is_boolean()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer"); try { return j[tag].get(); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } else return def; } inline std::string GET_STR(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (!j[tag].is_string()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be string"); try { return j[tag].get(); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory"); } inline int64_t GET_I64(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (!j[tag].is_number_integer()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer"); try { return j[tag].get(); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory"); } inline std::vector GET_STR_ARR(const nlohmann::json &j, const std::string& tag) { std::vector ret; if (CHECK_ARRAY(j, tag)) { try { for (const auto &iter: j[tag]) ret.push_back(iter.get()); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } return ret; } DetectorModuleGeometry::Direction GET_DIRECTION(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (!j[tag].is_string()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be string"); if (j[tag] == "Xp") return DetectorModuleGeometry::Direction::Xpos; if (j[tag] == "Xn") return DetectorModuleGeometry::Direction::Xneg; if (j[tag] == "Yp") return DetectorModuleGeometry::Direction::Ypos; if (j[tag] == "Yn") return DetectorModuleGeometry::Direction::Yneg; else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Allowed values for direction: Xp, Xn, Yp, Yn"); } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory"); } inline int64_t TimeToUs(const std::string &unit) { if ((unit == "us") || unit.empty()) return 1L; else if (unit == "ms") return 1000L; else if ((unit == "s") || (unit == "sec")) return 1000*1000L; else throw JFJochException(JFJochExceptionCategory::WrongUnit, "Only us, ms and s (or sec) are viable units for time"); } inline std::chrono::microseconds GET_TIME(const nlohmann::json &j, const std::string& tag) { if (j.contains(tag)) { if (j[tag].is_number()) return std::chrono::microseconds (std::lround(j[tag].get() * 1000.0 * 1000.0)); else if (j[tag].is_string()) { std::string::size_type pos; auto text = j[tag].get(); // Check if floating point pos = text.find_first_of('.'); if (pos != std::string::npos) throw JFJochException(JFJochExceptionCategory::WrongNumber, "Time must be provided as {s|ms|us|Hz} - no floating point allowed"); // Convert integer part int64_t val = std::stol(text, &pos); if (pos == 0) throw JFJochException(JFJochExceptionCategory::WrongNumber, "Time must be provided as {s|ms|us|Hz}"); pos = text.find_first_not_of(' ', pos); if (pos != std::string::npos) { if (text.substr(pos) == "Hz") val = 1000*1000L / val; else val *= TimeToUs(text.substr(pos)); } return std::chrono::microseconds(val); } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be number in seconds or string with time units s|ms|us"); } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory"); } DetectorGeometry ParseStandardDetectorGeometry(const nlohmann::json &j) { int32_t nmodules = GET_I64(j, "nmodules"); int32_t gap_x = GET_I64(j, "gap_x", 8); int32_t gap_y = GET_I64(j, "gap_y", 36); int32_t h_stacking = GET_I64(j, "horizontal_stacking", 2); bool mirror_y = GET_BOOL(j, "mirror_y", true); return {nmodules, h_stacking, gap_x, gap_y, mirror_y}; } DetectorGeometry ParseCustomDetectorGeometry(const nlohmann::json &j) { std::vector modules; for (const auto &iter: j) { if (!iter.is_object()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "custom_geometry element must be JSON object"); int64_t x0 = GET_I64(iter, "x0"); int64_t y0 = GET_I64(iter, "y0"); auto fast = GET_DIRECTION(iter, "fast_axis"); auto slow = GET_DIRECTION(iter, "slow_axis"); modules.emplace_back(x0, y0, fast, slow); } return {modules}; } DetectorGeometry ParseDetectorGeometry(const nlohmann::json &j) { if (CHECK_OBJECT(j, "standard_geometry")) return ParseStandardDetectorGeometry(j["standard_geometry"]); else if (CHECK_ARRAY(j, "custom_geometry")) return ParseCustomDetectorGeometry(j["custom_geometry"]); else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "It is compulsory to have either standard_detector or custom_detector declared."); } DetectorSetup ParseDetectorSetup(const nlohmann::json &j) { if (!j.is_object()) throw JFJochException(JFJochExceptionCategory::JSON, "Expecting JSON object for detector setup"); DetectorGeometry geom = ParseDetectorGeometry(j); std::string description = GET_STR(j, "description"); auto module_hostname = GET_STR_ARR(j, "module_hostname"); auto gain_files = GET_STR_ARR(j, "gain_files"); DetectorSetup setup(geom, description, module_hostname); if (!gain_files.empty()) setup.LoadGain(gain_files); setup.UDPInterfaceCount(GET_I64(j, "udp_interface_count", 2)); return setup; } void ParseDetectorSetup(const nlohmann::json &j, const std::string& tag, JFJochBroker& broker) { if (CHECK_ARRAY(j, tag)) { for (const auto &iter : j[tag]) broker.AddDetectorSetup(ParseDetectorSetup(iter)); } else throw JFJochException(JFJochExceptionCategory::JSON, "Detector setup not found"); } void ParseFacilityConfiguration(const nlohmann::json &input, const std::string& tag, DiffractionExperiment &experiment) { if (CHECK_OBJECT(input, tag)) { auto j = input[tag]; experiment.SourceName(GET_STR(j, "source_name")); experiment.SourceNameShort(GET_STR(j, "source_name_short")); experiment.InstrumentName(GET_STR(j, "instrument_name")); experiment.InstrumentNameShort(GET_STR(j, "instrument_name_short")); experiment.PedestalG0Frames(GET_I64(j, "pedestal_g0_frames")); experiment.PedestalG1Frames(GET_I64(j, "pedestal_g1_frames")); experiment.PedestalG2Frames(GET_I64(j, "pedestal_g2_frames")); experiment.FrameTime(GET_TIME(j, "frame_time_us"), GET_TIME(j, "count_time_us")); experiment.PreviewPeriod(GET_TIME(j, "preview_period_us")); experiment.IPv4BaseAddr(GET_STR(j, "detector_ipv4")); } else throw JFJochException(JFJochExceptionCategory::JSON, "Default configuration not found"); } void ParseAcquisitionDeviceGroup(const nlohmann::json &input, const std::string& tag, AcquisitionDeviceGroup &aq_devices) { if (CHECK_OBJECT(input, tag)) { const auto &j = input[tag]; std::string dev_type = GET_STR(j, "type"); int64_t buffer_size = GET_I64(j, "buffer_size", 1024); int64_t dev_count = GET_I64(j, "count", 1); if ((dev_count < 1) || (dev_count > 8)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Device count must be between 1 to 8"); if ((buffer_size < 16) || (buffer_size > 65536)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Buffer size must be in range 16-65536"); if (dev_type == "pcie") { for (int i = 0; i < dev_count; i++) aq_devices.AddPCIeDevice("/dev/jfjoch" + std::to_string(i)); } else if (dev_type == "mock") { for (int i = 0; i < dev_count; i++) aq_devices.AddMockDevice(buffer_size); } else if (dev_type == "hls") { if (dev_count > 1) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Device count must be 1 for HLS"); aq_devices.AddHLSDevice(buffer_size); } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Device type unknown"); std::vector ipv4_addr = GET_STR_ARR(j, "ipv4_addr"); if (ipv4_addr.size() != aq_devices.size()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch between number of devices and number of IPv4 addresses"); for (int i = 0; i < ipv4_addr.size(); i++) aq_devices[i].SetIPv4Address(IPv4AddressFromStr(ipv4_addr[i])); } } std::vector ParseStringArray(const nlohmann::json &input, const std::string& tag) { return GET_STR_ARR(input, tag); } std::string ParseString(const nlohmann::json &input, const std::string& tag) { if (input.contains(tag)) return GET_STR(input, tag); else return ""; } int64_t ParseInt64(const nlohmann::json &input, const std::string& tag, int64_t def) { return GET_I64(input, tag, def); }