// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include "DectrisSimplonClient.h" std::string to_string(SimplonState state) { switch (state) { case SimplonState::Na: return "Na"; case SimplonState::Ready: return "Ready"; case SimplonState::Initialize: return "Initialize"; case SimplonState::Configure: return "Configure"; case SimplonState::Acquire: return "Acquire"; case SimplonState::Idle: return "Idle"; case SimplonState::Test: return "Test"; case SimplonState::Error: return "Error"; default: return "Unknown"; } } DectrisSimplonClient::DectrisSimplonClient(const std::string &addr, uint16_t in_port) : hostname(addr), port(in_port) { } void DectrisSimplonClient::SendDetectorCommand(SimplonDetectorCommand cmd) { std::string key; switch (cmd) { case SimplonDetectorCommand::Initialize: key = "initialize"; break; case SimplonDetectorCommand::Arm: key = "arm"; break; case SimplonDetectorCommand::Disarm: key = "disarm"; break; case SimplonDetectorCommand::Trigger: key = "trigger"; break; case SimplonDetectorCommand::Cancel: key = "cancel"; break; } auto addr = GenAddr(SimplonModule::Detector, SimplonTask::Command, key); httplib::Client cli_cmd(hostname, port); cli_cmd.set_connection_timeout(std::chrono::milliseconds(500)); cli_cmd.set_read_timeout(std::chrono::minutes(20)); // Initialize or disarm might take a lot of time cli_cmd.set_write_timeout(std::chrono::minutes(20)); // Initialize or disarm might take a lot of time auto res = cli_cmd.Post(addr); if (!res || res->status != httplib::StatusCode::OK_200) { std::string err_msg = "Command failed " + key + "; HTTP " + std::to_string(res->status); if (res && !res->body.empty()) err_msg += "; " + res->body; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, err_msg); } } void DectrisSimplonClient::SetConfig(SimplonModule element, const std::string &key, const nlohmann::json &value) { std::string addr = GenAddr(element, SimplonTask::Config, key); nlohmann::json j; j["value"] = value; httplib::Client cli_cmd(hostname, port); cli_cmd.set_connection_timeout(std::chrono::seconds(1)); cli_cmd.set_read_timeout(std::chrono::minutes(20)); // Initialize or disarm might take a lot of time cli_cmd.set_write_timeout(std::chrono::minutes(20)); // Initialize or disarm might take a lot of time auto res = cli_cmd.Post(addr, j.dump(), "application/json"); if (!res || res->status != httplib::StatusCode::OK_200) { std::string err_msg = "Configure failed " + key + "; HTTP " + std::to_string(res->status); if (res && !res->body.empty()) err_msg += "; " + res->body; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, err_msg); } } std::string DectrisSimplonClient::GenAddr(DectrisSimplonClient::SimplonModule element, DectrisSimplonClient::SimplonTask task, const std::string& key) { std::string addr; switch (element) { case SimplonModule::Detector: addr += "/detector/"; break; case SimplonModule::Stream: addr += "/stream/"; break; case SimplonModule::Filewriter: addr += "/filewriter/"; break; case SimplonModule::Monitor: addr += "/monitor/"; break; } addr += "api/"; addr += api_version; switch (task) { case SimplonTask::Config: addr += "/config/"; break; case SimplonTask::Command: addr += "/command/"; break; case SimplonTask::Status: addr += "/status/"; break; } addr += key; return addr; } SimplonConfig DectrisSimplonClient::GetConfig(DectrisSimplonClient::SimplonModule element, DectrisSimplonClient::SimplonTask task, const std::string &key) { httplib::Client cli_simple(hostname, port); cli_simple.set_connection_timeout(std::chrono::milliseconds(500)); cli_simple.set_read_timeout(std::chrono::seconds(1)); cli_simple.set_write_timeout(std::chrono::seconds(1)); auto res = cli_simple.Get(GenAddr(element, task, key)); if (!res) { throw JFJochException(JFJochExceptionCategory::SimplonError, "Get key " + key + " timeout/error: " + to_string(res.error())); } if (res->status != httplib::StatusCode::OK_200) { throw JFJochException(JFJochExceptionCategory::SimplonError, "Get key " + key + " failed: " + to_string(res.error())); } try { auto j = nlohmann::json::parse(res->body); SimplonConfig ret{}; ret.val = j["value"]; if (j.contains("unit")) ret.unit = j["unit"]; if (j.contains("min")) ret.min = j["min"]; if (j.contains("max")) ret.max = j["max"]; return ret; } catch (const std::exception &e) { throw JFJochException(JFJochExceptionCategory::JSON, e.what()); } } SimplonConfig DectrisSimplonClient::GetConfig(SimplonModule element, const std::string &key) { return GetConfig(element, SimplonTask::Config, key); } SimplonConfig DectrisSimplonClient::GetDetCfg(const std::string &key) { return GetConfig(SimplonModule::Detector, key); } nlohmann::json DectrisSimplonClient::GetStatus(DectrisSimplonClient::SimplonModule element, const std::string &key) { return GetConfig(element, SimplonTask::Status, key).val; } void DectrisSimplonClient::ReadDetectorConfig(DetectorSetup &setup) { // Need to initialize to read configuration information if ((GetState() == SimplonState::Na) || (GetState() == SimplonState::Error)) throw JFJochException(JFJochExceptionCategory::Detector, "Detector state Na or Error - need to reinitialize before reading configuration"); setup.Description(GetDetCfg( "description").val); setup.PixelSize_um(std::round(GetDetCfg( "x_pixel_size").val.get() * 1e6f)); setup.SerialNumber(GetDetCfg( "detector_number").val); setup.SensorMaterial(GetDetCfg( "sensor_material").val); setup.SensorThickness_um(std::round(GetDetCfg( "sensor_thickness").val.get() * 1e6f)); setup.BitDepthReadout(GetDetCfg("bit_depth_readout").val.get()); setup.BitDepthImage(GetDetCfg("bit_depth_image").val.get()); setup.MinFrameTime(std::chrono::microseconds(std::lround(GetDetCfg("frame_time").min.get() * 1e6f))); setup.MinCountTime(std::chrono::microseconds(std::lround(GetDetCfg("count_time").min.get() * 1e6f))); setup.ReadOutTime(std::chrono::microseconds(std::lround(GetDetCfg("detector_readout_time").val.get() * 1e6f))); nlohmann::json min_threshold_energy = GetDetCfg("threshold_energy").min; if (min_threshold_energy.is_number()) setup.MinThreshold_keV(min_threshold_energy.get() / 1000.0f); else setup.MinThreshold_keV(2.7f); int64_t width = GetDetCfg("x_pixels_in_detector").val.get(); int64_t height = GetDetCfg( "y_pixels_in_detector").val.get(); setup.Geometry(DetectorGeometryFixed(width, height)); } void DectrisSimplonClient::InitializeDetector(DetectorSetup& setup) { auto state = GetState(); if ((state == SimplonState::Na) || (state == SimplonState::Error)) SendDetectorCommand(SimplonDetectorCommand::Initialize); if (GetState() != SimplonState::Idle) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Initialize unsuccessful"); SetConfig(SimplonModule::Detector, "roi_mode", setup.GetDECTRISROI()); ReadDetectorConfig(setup); } void DectrisSimplonClient::TriggerAcquisition() { SendDetectorCommand(SimplonDetectorCommand::Trigger); } void DectrisSimplonClient::CancelAcquisition() { SendDetectorCommand(SimplonDetectorCommand::Cancel); } void DectrisSimplonClient::EndAcquisitionFinished() { SendDetectorCommand(SimplonDetectorCommand::Disarm); } void DectrisSimplonClient::StartAcquisition() { SendDetectorCommand(SimplonDetectorCommand::Arm); } void DectrisSimplonClient::ConfigureDetector(const DiffractionExperiment &experiment) { SetConfig(SimplonModule::Stream, "format", "cbor"); SetConfig(SimplonModule::Stream, "mode", "enabled"); SetConfig(SimplonModule::Filewriter, "mode", "disabled"); SetConfig(SimplonModule::Monitor, "mode", "disabled"); SetConfig(SimplonModule::Detector, "photon_energy", experiment.GetIncidentEnergy_keV() * 1e3f); auto thr = experiment.GetEigerThreshold_keV(); SetConfig(SimplonModule::Detector, "threshold_energy", thr * 1e3f); switch (experiment.GetDetectorTiming()) { case DetectorTiming::Auto: SetConfig(SimplonModule::Detector, "trigger_mode", "ints"); break; case DetectorTiming::Trigger: SetConfig(SimplonModule::Detector, "trigger_mode", "exts"); break; case DetectorTiming::Gated: SetConfig(SimplonModule::Detector, "trigger_mode", "extg"); break; case DetectorTiming::Burst: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Burst mode not supported with DECTRIS sytems"); } // For DECTRIS detectors we assume frame_time == count_time if (experiment.GetFrameCountTimeAuto()) SetConfig(SimplonModule::Detector, "count_time", experiment.GetFrameTime().count() / 1e6f); else SetConfig(SimplonModule::Detector, "count_time", experiment.GetFrameCountTime().count() / 1e6f); SetConfig(SimplonModule::Detector, "frame_time", experiment.GetFrameTime().count() / 1e6f); SetConfig(SimplonModule::Detector, "nimages", experiment.GetFrameNumPerTrigger()); SetConfig(SimplonModule::Detector, "ntrigger", experiment.GetNumTriggers()); SetConfig(SimplonModule::Detector, "beam_center_x", experiment.GetBeamX_pxl()); SetConfig(SimplonModule::Detector, "beam_center_y", experiment.GetBeamY_pxl()); SetConfig(SimplonModule::Detector, "detector_distance", experiment.GetDetectorDistance_mm() / 1e3f); SetConfig(SimplonModule::Detector, "trigger_start_delay", experiment.GetDetectorDelay().count() / 1e9); } SimplonState DectrisSimplonClient::GetState() { auto c = GetStatus(SimplonModule::Detector, "state"); if (!c.is_string()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "value must be string"); std::string res = c.get(); if (res == "na") return SimplonState::Na; else if (res == "ready") return SimplonState::Ready; else if (res == "idle") return SimplonState::Idle; else if (res == "acquire") return SimplonState::Acquire; else if (res == "error") return SimplonState::Error; else if (res == "initialize") return SimplonState::Initialize; else if (res == "configure") return SimplonState::Configure; else if (res == "test") return SimplonState::Test; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "State unknown " + res); } float DectrisSimplonClient::GetHumidity() { return GetStatus(SimplonModule::Detector, "humidity"); } float DectrisSimplonClient::GetTemperature() { return GetStatus(SimplonModule::Detector, "temperature"); } std::vector DectrisSimplonClient::GetPixelMask() { auto mask = GetDetCfg("pixel_mask"); if (!mask.val.is_object() || !mask.val.contains("shape") || !mask.val.contains("data")) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "No pixel mask"); auto shape = mask.val["shape"]; std::vector pixel_mask(shape[0].get() * shape[1].get()); macaron::Decode(mask.val["data"], pixel_mask); return pixel_mask; }