// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only // Using OpenAPI licensed with Apache License 2.0 #include #include "JFJochBrokerHttp.h" #include "gen/model/Error_message.h" #include "../common/GitInfo.h" #include "OpenAPIConvert.h" #include "../preview/JFJochTIFF.h" JFJochBrokerHttp::JFJochBrokerHttp(const DiffractionExperiment &experiment, std::shared_ptr &rtr) : DefaultApi(rtr) { Pistache::Rest::Routes::Get(*rtr, "/", Pistache::Rest::Routes::bind(&JFJochBrokerHttp::GetStaticFile, this)); Pistache::Rest::Routes::Get(*rtr, "/frontend", Pistache::Rest::Routes::bind(&JFJochBrokerHttp::GetStaticFile, this)); Pistache::Rest::Routes::Get(*rtr, "/frontend/*", Pistache::Rest::Routes::bind(&JFJochBrokerHttp::GetStaticFile, this)); Pistache::Rest::Routes::Get(*rtr, "/frontend/assets/*", Pistache::Rest::Routes::bind(&JFJochBrokerHttp::GetStaticFile, this)); state_machine.NotThreadSafe_Experiment() = experiment; init(); } void JFJochBrokerHttp::AddDetectorSetup(const DetectorSetup &setup) { state_machine.AddDetectorSetup(setup); logger.Info("Added detector {}", setup.GetDescription()); } JFJochServices &JFJochBrokerHttp::Services() { return services; } void JFJochBrokerHttp::cancel_post(Pistache::Http::ResponseWriter &response) { state_machine.Cancel(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::deactivate_post(Pistache::Http::ResponseWriter &response) { state_machine.Deactivate(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::initialize_post(Pistache::Http::ResponseWriter &response) { state_machine.Initialize(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::start_post(const org::openapitools::server::model::Dataset_settings &datasetSettings, Pistache::Http::ResponseWriter &response) { nlohmann::json j = datasetSettings; logger.Info("Start {}", j.dump()); state_machine.Start(Convert(datasetSettings)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::status_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetStatus()), response); } void JFJochBrokerHttp::wait_till_done_post(const std::optional &timeout, Pistache::Http::ResponseWriter &response) { JFJochState state; if (!timeout) state = state_machine.WaitTillMeasurementDone(std::chrono::minutes(1)); else if ((timeout.value() > 3600) || (timeout.value() < 0)) { response.send(Pistache::Http::Code::Bad_Request); return; } else if (timeout.value() == 0) state = state_machine.GetState(); else state = state_machine.WaitTillMeasurementDone(std::chrono::seconds(timeout.value())); switch (state) { case JFJochState::Idle: response.send(Pistache::Http::Code::Ok); break; case JFJochState::Inactive: response.send(Pistache::Http::Code::Bad_Gateway); break; case JFJochState::Error: response.send(Pistache::Http::Code::Internal_Server_Error); break; case JFJochState::Measuring: case JFJochState::Busy: case JFJochState::Pedestal: response.send(Pistache::Http::Code::Gateway_Timeout); break; } } void JFJochBrokerHttp::trigger_post(Pistache::Http::ResponseWriter &response) { state_machine.Trigger(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::pedestal_post(Pistache::Http::ResponseWriter &response) { state_machine.Pedestal(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_detector_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetDetectorSettings()), response); } void JFJochBrokerHttp::config_detector_put(const org::openapitools::server::model::Detector_settings &detectorSettings, Pistache::Http::ResponseWriter &response) { state_machine.LoadDetectorSettings(Convert(detectorSettings)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_azim_int_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetRadialIntegrationSettings()), response); } void JFJochBrokerHttp::config_azim_int_put(const org::openapitools::server::model::Azim_int_settings &radIntSettings, Pistache::Http::ResponseWriter &response) { state_machine.SetRadialIntegrationSettings(Convert(radIntSettings)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_select_detector_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetDetectorsList()), response); } void JFJochBrokerHttp::config_select_detector_put( const org::openapitools::server::model::Detector_selection &detectorSelection, Pistache::Http::ResponseWriter &response) { state_machine.SelectDetector(detectorSelection.getId()); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_spot_finding_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetSpotFindingSettings()), response); } void JFJochBrokerHttp::config_spot_finding_put( const org::openapitools::server::model::Spot_finding_settings &spotFindingSettings, Pistache::Http::ResponseWriter &response) { state_machine.SetSpotFindingSettings(Convert(spotFindingSettings)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::plot_azim_int_get(const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::AzInt, 0, compression, response); } void JFJochBrokerHttp::statistics_calibration_get(Pistache::Http::ResponseWriter &response) { nlohmann::json j; for (const auto &d: Convert(state_machine.GetCalibrationStatistics())) j.push_back(d); response.send(Pistache::Http::Code::Ok, j.dump(), MIME(Application, Json)); } void JFJochBrokerHttp::statistics_data_collection_get(Pistache::Http::ResponseWriter &response) { auto stats = state_machine.GetMeasurementStatistics(); if (stats) { ProcessOutput(Convert(stats.value()), response); } else { response.send(Pistache::Http::Code::Not_Found); } } std::pair JFJochBrokerHttp::handleOperationException(const std::exception &ex) const noexcept { try { throw; } catch (const WrongDAQStateException &e) { org::openapitools::server::model::Error_message msg; msg.setMsg(ex.what()); msg.setReason("WrongDAQState"); nlohmann::json j; to_json(j, msg); return std::make_pair(Pistache::Http::Code::Internal_Server_Error, j.dump()); } catch (const std::exception &e) { org::openapitools::server::model::Error_message msg; msg.setMsg(ex.what()); msg.setReason("Other"); nlohmann::json j; to_json(j, msg); return std::make_pair(Pistache::Http::Code::Internal_Server_Error, j.dump()); } } void JFJochBrokerHttp::GetStaticFile(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response) { if (!frontend_directory.empty()) { logger.Info("Requesting static resource {} from {}", request.resource(), frontend_directory); if (request.resource().find("../") != std::string::npos) response.send(Pistache::Http::Code::Forbidden); try { if ((request.resource() == "/") || (request.resource() == "/frontend") || (request.resource() == "/frontend/")) Pistache::Http::serveFile(response, frontend_directory + "/index.html", MIME(Text, Html)); else if (request.resource().starts_with("/frontend/")) { if (request.resource().ends_with(".js")) Pistache::Http::serveFile(response, frontend_directory + "/" + request.resource().substr(10), MIME(Text,Javascript)); else if (request.resource().ends_with(".css")) Pistache::Http::serveFile(response, frontend_directory + "/" + request.resource().substr(10), MIME(Text,Css)); else if (request.resource().ends_with(".html")) Pistache::Http::serveFile(response, frontend_directory + "/" + request.resource().substr(10), MIME(Text,Html)); else Pistache::Http::serveFile(response, frontend_directory + "/" + request.resource().substr(10)); } else response.send(Pistache::Http::Code::Not_Found); } catch (const std::exception &e) { logger.Error(e.what()); response.send(Pistache::Http::Code::Not_Found); } } else response.send(Pistache::Http::Code::Not_Found); } JFJochBrokerHttp &JFJochBrokerHttp::FrontendDirectory(const std::string &directory) { frontend_directory = directory; return *this; } void JFJochBrokerHttp::detector_status_get(Pistache::Http::ResponseWriter &response) { auto out = state_machine.GetDetectorStatus(); if (out) ProcessOutput(Convert(out.value()), response); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::preview_calibration_tiff_get(Pistache::Http::ResponseWriter &response) { std::string s = state_machine.GetPreviewTIFF(true); if (!s.empty()) response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/tiff")); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::preview_image_jpeg_post(const org::openapitools::server::model::Preview_settings &previewSettings, Pistache::Http::ResponseWriter &response) { std::string s = state_machine.GetPreviewJPEG(Convert(previewSettings)); if (!s.empty()) response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/jpeg")); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::preview_image_jpeg_get(Pistache::Http::ResponseWriter &response) { std::string s = state_machine.GetPreviewJPEG(PreviewJPEGSettings()); if (!s.empty()) response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/jpeg")); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::preview_image_tiff_get(Pistache::Http::ResponseWriter &response) { std::string s = state_machine.GetPreviewTIFF(false); if (!s.empty()) response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/tiff")); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::config_internal_generator_image_put(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) { int64_t image_number = 0; auto number_query = request.query().get("id"); if (number_query) image_number = std::stoi(number_query.value()); if ((image_number < 0) || (image_number > 127)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "id must be in range 0-127"); state_machine.LoadInternalGeneratorImage(request.body().data(), request.body().size(), image_number); logger.Info("Internal generator image #{} loaded", image_number); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_internal_generator_image_tiff_put(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) { int64_t image_number = 0; auto number_query = request.query().get("id"); if (number_query) image_number = std::stoi(number_query.value()); if ((image_number < 0) || (image_number > 127)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "id must be in range 0-127"); state_machine.LoadInternalGeneratorImageTIFF(request.body(), image_number); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::roi_box_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetBoxROI()), response); } void JFJochBrokerHttp::roi_box_put(const org::openapitools::server::model::Roi_box_list &roiBoxList, Pistache::Http::ResponseWriter &response) { state_machine.SetBoxROI(Convert(roiBoxList)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::roi_circle_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetCircleROI()), response); } void JFJochBrokerHttp::roi_circle_put(const org::openapitools::server::model::Roi_circle_list &roiCircleList, Pistache::Http::ResponseWriter &response) { state_machine.SetCircleROI(Convert(roiCircleList)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::xfel_event_code_get(Pistache::Http::ResponseWriter &response) { auto array = state_machine.GetXFELEventCode(); if (array.empty()) response.send(Pistache::Http::Code::Not_Found); nlohmann::json j = array; response.send(Pistache::Http::Code::Ok, j.dump(), MIME(Application, Json)); } void JFJochBrokerHttp::xfel_pulse_id_get(Pistache::Http::ResponseWriter &response) { auto array = state_machine.GetXFELPulseID(); if (array.empty()) response.send(Pistache::Http::Code::Not_Found); nlohmann::json j = array; response.send(Pistache::Http::Code::Ok, j.dump(), MIME(Application, Json)); } void JFJochBrokerHttp::preview_pedestal_tiff_get(const std::optional &gainLevel, const std::optional &sc, Pistache::Http::ResponseWriter &response) { if (!gainLevel) response.send(Pistache::Http::Code::Bad_Request); std::string s = state_machine.GetPedestalTIFF(gainLevel.value(), sc ? sc.value() : 0); if (!s.empty()) response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/tiff")); else response.send(Pistache::Http::Code::Not_Found); } void JFJochBrokerHttp::GenericPlot(PlotType plot_type, const std::optional &binning, const std::optional& in_compression, Pistache::Http::ResponseWriter &response) { bool compression = !in_compression.has_value() || in_compression.value(); PlotRequest req{.type = plot_type, .binning = 0}; if (binning) { if (binning.value() < 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Binning must be positive number or zero"); req.binning = binning.value(); } auto plot = state_machine.GetPlots(req); ProcessOutput(Convert(plot), response, compression); } void JFJochBrokerHttp::plot_bkg_estimate_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::BkgEstimate, binning, compression, response); } void JFJochBrokerHttp::plot_error_pixel_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ErrorPixels, binning, compression, response); } void JFJochBrokerHttp::plot_image_collection_efficiency_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ImageCollectionEfficiency, binning, compression, response); } void JFJochBrokerHttp::plot_indexing_rate_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::IndexingRate, binning, compression, response); } void JFJochBrokerHttp::plot_receiver_delay_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ReceiverDelay, binning, compression, response); } void JFJochBrokerHttp::plot_receiver_free_send_buffers_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ReceiverFreeSendBuf, binning, compression, response); } void JFJochBrokerHttp::plot_roi_max_count_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ROIMaxCount, binning, compression, response); } void JFJochBrokerHttp::plot_roi_sum_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ROISum, binning, compression, response); } void JFJochBrokerHttp::plot_roi_valid_pixels_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::ROIPixels, binning, compression, response); } void JFJochBrokerHttp::plot_spot_count_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::SpotCount, binning, compression, response); } void JFJochBrokerHttp::plot_strong_pixel_get(const std::optional &binning, const std::optional& compression, Pistache::Http::ResponseWriter &response) { GenericPlot(PlotType::StrongPixels, binning, compression, response); } void JFJochBrokerHttp::config_mask_tiff_get(Pistache::Http::ResponseWriter &response) { auto s = state_machine.GetFullPixelMaskTIFF(); response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/tiff")); } void JFJochBrokerHttp::config_user_mask_tiff_get(Pistache::Http::ResponseWriter &response) { auto s = state_machine.GetUserPixelMaskTIFF(); response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/tiff")); } void JFJochBrokerHttp::config_user_mask_tiff_put(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) { uint32_t cols, lines; auto v = ReadTIFFFromString32(request.body(), cols, lines); state_machine.SetUserPixelMask(v); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_mask_get(Pistache::Http::ResponseWriter &response) { const auto v = state_machine.GetFullPixelMask(); response.send(Pistache::Http::Code::Ok, reinterpret_cast(v.data()), v.size() * sizeof(uint32_t), Pistache::Http::Mime::MediaType::fromString("application/octet-stream")); } void JFJochBrokerHttp::config_user_mask_get(Pistache::Http::ResponseWriter &response) { const auto v = state_machine.GetUserPixelMask(); response.send(Pistache::Http::Code::Ok, reinterpret_cast(v.data()), v.size() * sizeof(uint32_t), Pistache::Http::Mime::MediaType::fromString("application/octet-stream")); } void JFJochBrokerHttp::config_user_mask_put(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) { if (request.body().empty()) { response.send(Pistache::Http::Code::Bad_Request, "Request body cannot be empty"); return; } if (request.body().size() % 4 != 0) { response.send(Pistache::Http::Code::Bad_Request, "Request has to be 32-bit"); return; } std::vector v(request.body().size() / 4); memcpy(v.data(), request.body().data(), request.body().size()); state_machine.SetUserPixelMask(v); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::version_get(Pistache::Http::ResponseWriter &response) { response.send(Pistache::Http::Code::Ok, jfjoch_version(), MIME(Text, Plain)); } void JFJochBrokerHttp::config_instrument_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetInstrumentMetadata()), response); } void JFJochBrokerHttp::config_instrument_put(const org::openapitools::server::model::Instrument_metadata &instrumentMetadata,Pistache::Http::ResponseWriter &response) { state_machine.LoadInstrumentMetadata(Convert(instrumentMetadata)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_image_format_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetImageFormatSettings()), response); } void JFJochBrokerHttp::config_image_format_put( const org::openapitools::server::model::Image_format_settings &imageFormatSettings, Pistache::Http::ResponseWriter &response) { state_machine.LoadImageFormatSettings(Convert(imageFormatSettings)); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_image_format_conversion_post(Pistache::Http::ResponseWriter &response) { state_machine.ConvImageFormatSettings(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::config_image_format_raw_post(Pistache::Http::ResponseWriter &response) { state_machine.RawImageFormatSettings(); response.send(Pistache::Http::Code::Ok); } void JFJochBrokerHttp::fpga_status_get(Pistache::Http::ResponseWriter &response) { nlohmann::json j; for (const auto &d: Convert(state_machine.GetDeviceStatus())) j.push_back(d); response.send(Pistache::Http::Code::Ok, j.dump(), MIME(Application, Json)); } void JFJochBrokerHttp::statistics_get(const std::optional &compression, Pistache::Http::ResponseWriter &response) { org::openapitools::server::model::Jfjoch_statistics statistics; auto data_collection_statistics = state_machine.GetMeasurementStatistics(); if (data_collection_statistics) statistics.setMeasurement(Convert(data_collection_statistics.value())); statistics.setFpga(Convert(state_machine.GetDeviceStatus())); statistics.setCalibration(Convert(state_machine.GetCalibrationStatistics())); statistics.setBroker(Convert(state_machine.GetStatus())); auto det_status = state_machine.GetDetectorStatus(); if (det_status.has_value()) statistics.setDetector(Convert(det_status.value())); statistics.setDetectorSettings(Convert(state_machine.GetDetectorSettings())); statistics.setDetectorList(Convert(state_machine.GetDetectorsList())); statistics.setDataProcessingSettings(Convert(state_machine.GetSpotFindingSettings())); statistics.setInstrumentMetadata(Convert(state_machine.GetInstrumentMetadata())); statistics.setImageFormatSettings(Convert(state_machine.GetImageFormatSettings())); statistics.setPixelMask(Convert(state_machine.GetPixelMaskStatistics())); auto zeromq_prev = state_machine.GetPreviewSocketSettings(); if (!zeromq_prev.address.empty()) statistics.setZeromqPreview(Convert(zeromq_prev)); nlohmann::json j = statistics; if (!compression.has_value() || compression.value()) response.setCompression(Pistache::Http::Header::Encoding::Deflate); response.send(Pistache::Http::Code::Ok, j.dump(), MIME(Application, Json)); } void JFJochBrokerHttp::config_zeromq_preview_get(Pistache::Http::ResponseWriter &response) { ProcessOutput(Convert(state_machine.GetPreviewSocketSettings()), response); } void JFJochBrokerHttp::config_zeromq_preview_put( const org::openapitools::server::model::Zeromq_preview_settings &zeromqPreviewSettings, Pistache::Http::ResponseWriter &response) { state_machine.SetPreviewSocketSettings(Convert(zeromqPreviewSettings)); response.send(Pistache::Http::Code::Ok); }