239a441ee6
Build Packages / Unit tests (push) Successful in 1h20m34s
Build Packages / build:rpm (rocky8) (push) Successful in 13m32s
Build Packages / Generate python client (push) Successful in 24s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m6s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m32s
Build Packages / XDS test (durin plugin) (push) Successful in 10m49s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m8s
Build Packages / DIALS test (push) Successful in 14m57s
Build Packages / Build documentation (push) Successful in 47s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m30s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m23s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m40s
Build Packages / Create release (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m14s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 11m55s
Build Packages / build:rpm (rocky9) (push) Successful in 14m23s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m10s
This is an UNSTABLE release. The release has significant modifications and bug fixes, if things go wrong, it is better to revert to 1.0.0-rc.132. * jfjoch_broker: For DECTRIS detectors, ZeroMQ link is persistent, to save time for establishing new connection * jfjoch_broker: Minor bug fixes for rare conditions Reviewed-on: #50
336 lines
13 KiB
C++
336 lines
13 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <base64/Base64.h>
|
|
#include <httplib.h>
|
|
|
|
#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;
|
|
if (res) {
|
|
err_msg += "; HTTP" + std::to_string(res->status);
|
|
if (!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;
|
|
if (res) {
|
|
err_msg += "; HTTP" + std::to_string(res->status);
|
|
if (!res->body.empty())
|
|
err_msg += "; " + res->body;
|
|
}
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, err_msg);
|
|
}
|
|
}
|
|
|
|
void DectrisSimplonClient::SetConfigIfDifferent(SimplonModule element, const std::string &key, float value, float tolerance) {
|
|
auto curr_val = GetConfig(element, key).val.get<float>();
|
|
|
|
if (std::abs(curr_val - value) > tolerance)
|
|
SetConfig(element, key, value);
|
|
}
|
|
|
|
void DectrisSimplonClient::SetConfigIfDifferent(SimplonModule element, const std::string &key, int64_t value) {
|
|
auto curr_val = GetConfig(element, key).val.get<int64_t>();
|
|
if (curr_val != value)
|
|
SetConfig(element, key, value);
|
|
}
|
|
|
|
std::string DectrisSimplonClient::GenAddr(SimplonModule element, 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(SimplonModule element, 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(SimplonModule element, const std::string &key) {
|
|
return GetConfig(element, SimplonTask::Status, key).val;
|
|
}
|
|
|
|
template <class T>
|
|
T float2time(float time_in_seconds) {
|
|
return std::chrono::duration_cast<T>(std::chrono::duration<float>(time_in_seconds));
|
|
}
|
|
|
|
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<float>() * 1e6f));
|
|
setup.SerialNumber(GetDetCfg( "detector_number").val);
|
|
setup.SensorMaterial(GetDetCfg( "sensor_material").val);
|
|
setup.SensorThickness_um(std::round(GetDetCfg( "sensor_thickness").val.get<float>() * 1e6f));
|
|
|
|
setup.BitDepthReadout(GetDetCfg("bit_depth_readout").val.get<int64_t>());
|
|
setup.BitDepthImage(GetDetCfg("bit_depth_image").val.get<int64_t>());
|
|
setup.MinFrameTime(float2time<std::chrono::nanoseconds>(GetDetCfg("frame_time").min.get<float>()));
|
|
setup.MinCountTime(float2time<std::chrono::nanoseconds>(GetDetCfg("count_time").min.get<float>()));
|
|
setup.ReadOutTime(float2time<std::chrono::nanoseconds>(GetDetCfg("detector_readout_time").val.get<float>()));
|
|
|
|
nlohmann::json min_threshold_energy = GetDetCfg("threshold_energy").min;
|
|
if (min_threshold_energy.is_number())
|
|
setup.MinThreshold_keV(min_threshold_energy.get<float>() / 1000.0f);
|
|
else
|
|
setup.MinThreshold_keV(2.7f);
|
|
|
|
int64_t width = GetDetCfg("x_pixels_in_detector").val.get<int64_t>();
|
|
int64_t height = GetDetCfg( "y_pixels_in_detector").val.get<int64_t>();
|
|
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());
|
|
// Configure streaming, disable file writer and monitor
|
|
SetConfig(SimplonModule::Stream, "format", "cbor");
|
|
SetConfig(SimplonModule::Stream, "mode", "enabled");
|
|
SetConfig(SimplonModule::Filewriter, "mode", "disabled");
|
|
SetConfig(SimplonModule::Monitor, "mode", "disabled");
|
|
ReadDetectorConfig(setup);
|
|
}
|
|
|
|
void DectrisSimplonClient::TriggerAcquisition() {
|
|
SendDetectorCommand(SimplonDetectorCommand::Trigger);
|
|
}
|
|
|
|
void DectrisSimplonClient::CancelAcquisition() {
|
|
SendDetectorCommand(SimplonDetectorCommand::Cancel);
|
|
}
|
|
|
|
void DectrisSimplonClient::EndAcquisitionFinished() {
|
|
SendDetectorCommand(SimplonDetectorCommand::Disarm);
|
|
}
|
|
|
|
void DectrisSimplonClient::StartAcquisition(const DiffractionExperiment& experiment) {
|
|
SetConfigIfDifferent(SimplonModule::Detector, "count_time", std::chrono::duration<float>(experiment.GetFrameCountTime()).count(), 1e-9);
|
|
SetConfigIfDifferent(SimplonModule::Detector, "frame_time", std::chrono::duration<float>(experiment.GetFrameTime()).count(), 1e-9);
|
|
SetConfigIfDifferent(SimplonModule::Detector, "nimages", experiment.GetFrameNumPerTrigger());
|
|
SetConfigIfDifferent(SimplonModule::Detector, "ntrigger", experiment.GetNumTriggers());
|
|
|
|
SendDetectorCommand(SimplonDetectorCommand::Arm);
|
|
}
|
|
|
|
void DectrisSimplonClient::ConfigureDetector(const DiffractionExperiment &experiment) {
|
|
// TODO: Check if counting_mode retrigger is available
|
|
if (experiment.GetDetectorMode() == DetectorMode::DarkMask)
|
|
SetConfig(SimplonModule::Detector, "counting_mode", "normal");
|
|
else
|
|
SetConfig(SimplonModule::Detector, "counting_mode", "retrigger");
|
|
|
|
SetConfigIfDifferent(SimplonModule::Detector, "photon_energy", experiment.GetIncidentEnergy_keV() * 1e3f, 0.01f);
|
|
auto thr = experiment.GetEigerThreshold_keV();
|
|
SetConfigIfDifferent(SimplonModule::Detector, "threshold_energy", thr * 1e3f, 0.01);
|
|
|
|
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");
|
|
}
|
|
|
|
SetConfig(SimplonModule::Detector, "trigger_start_delay", std::chrono::duration<float>(experiment.GetDetectorDelay()).count());
|
|
}
|
|
|
|
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<std::string>();
|
|
|
|
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<uint32_t> 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<uint32_t> pixel_mask(shape[0].get<int64_t>() * shape[1].get<int64_t>());
|
|
macaron::Decode(mask.val["data"], pixel_mask);
|
|
|
|
return pixel_mask;
|
|
} |