// Copyright (2019-2022) Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-or-later #include #include "JFJochStateMachine.h" #include "../common/JFJochException.h" JFJochStateMachine::JFJochStateMachine(JFJochServices &in_services, Logger &in_logger) : services(in_services), logger(in_logger), data_processing_settings(DiffractionExperiment::DefaultDataProcessingSettings()) { } void JFJochStateMachine::ImportPedestalG0(const JFJochProtoBuf::ReceiverOutput &receiver_output) { if (receiver_output.pedestal_result_size() != experiment.GetModulesNum() * experiment.GetStorageCellNumber()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in pedestal output"); for (int s = 0; s < experiment.GetStorageCellNumber(); s++) { for (int module = 0; module < experiment.GetModulesNum(); module++) calibration->Pedestal(module, 0, s) = receiver_output.pedestal_result(module + s * experiment.GetModulesNum()); } SetCalibrationStatistics(calibration->GetModuleStatistics()); } void JFJochStateMachine::ImportPedestal(const JFJochProtoBuf::ReceiverOutput &receiver_output, size_t gain_level, size_t storage_cell) { for (int i = 0; i < receiver_output.pedestal_result_size(); i++) calibration->Pedestal(i, gain_level, storage_cell) = receiver_output.pedestal_result(i); SetCalibrationStatistics(calibration->GetModuleStatistics()); } void JFJochStateMachine::TakePedestalInternalAll(std::unique_lock &ul) { calibration = std::make_unique(experiment); if (!gain_calibration.empty()) { if (gain_calibration.size() != experiment.GetModulesNum()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in gain files number"); for (int i = 0; i < gain_calibration.size(); i++) calibration->GainCalibration(i) = gain_calibration[i]; } cancel_sequence = false; logger.Info("Pedestal sequence started"); try { TakePedestalInternalG0(ul); for (int i = 0; i < experiment.GetStorageCellNumber(); i++) { TakePedestalInternalG1(ul, i); TakePedestalInternalG2(ul, i); } } catch (...) { logger.Info("Pedestal sequence error"); state = JFJochState::Error; throw; } logger.Info("Pedestal sequence done"); } void JFJochStateMachine::TakePedestalInternalG0(std::unique_lock &ul) { state = JFJochState::Pedestal; DiffractionExperiment local_experiment(experiment); local_experiment.Mode(DetectorMode::PedestalG0); local_experiment.StorageCellStart(16 - local_experiment.GetStorageCellNumber()); if (!cancel_sequence && (local_experiment.GetPedestalG0Frames() > 0)) { services.Start(local_experiment, *calibration); services.Trigger(); ul.unlock(); // Allow to cancel/abort during the pedestal data collection // Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock auto pedestal_output = services.Stop(*calibration); ul.lock(); // SetFullMeasurementOutput(pedestal_output); ImportPedestalG0(pedestal_output.receiver()); } state = JFJochState::Idle; } void JFJochStateMachine::TakePedestalInternalG1(std::unique_lock &ul, int32_t storage_cell) { state = JFJochState::Pedestal; DiffractionExperiment local_experiment(experiment); local_experiment.Mode(DetectorMode::PedestalG1); if (local_experiment.GetStorageCellNumber() == 2) local_experiment.StorageCellStart((storage_cell + 15) % 16); // one previous if (!cancel_sequence && (local_experiment.GetPedestalG1Frames() > 0)) { services.Start(local_experiment, *calibration); services.Trigger(); ul.unlock(); // Allow to cancel/abort during the pedestal data collection // Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock auto pedestal_output = services.Stop(*calibration); ul.lock(); // SetFullMeasurementOutput(pedestal_output); ImportPedestal(pedestal_output.receiver(), 1, storage_cell); } state = JFJochState::Idle; } void JFJochStateMachine::TakePedestalInternalG2(std::unique_lock &ul, int32_t storage_cell) { state = JFJochState::Pedestal; DiffractionExperiment local_experiment(experiment); local_experiment.Mode(DetectorMode::PedestalG2); if (local_experiment.GetStorageCellNumber() == 2) local_experiment.StorageCellStart((storage_cell + 15) % 16); // one previous if (!cancel_sequence && (local_experiment.GetPedestalG2Frames() > 0)) { services.Start(local_experiment, *calibration); services.Trigger(); ul.unlock(); // Allow to cancel/abort during the pedestal data collection // Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock auto pedestal_output = services.Stop(*calibration); ul.lock(); // SetFullMeasurementOutput(pedestal_output); ImportPedestal(pedestal_output.receiver(), 2, storage_cell); } state = JFJochState::Idle; } void JFJochStateMachine::Initialize() { std::unique_lock ul(m); if ((state == JFJochState::Measuring) || (state == JFJochState::Pedestal)) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Cannot initialize during measurement"); if (detector_setup.empty()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector information not provided"); logger.Info("Initialize"); state = JFJochState::Busy; ClearMeasurementStatistics(); try { services.On(experiment); } catch (...) { state = JFJochState::Error; throw; } TakePedestalInternalAll(ul); } void JFJochStateMachine::Pedestal() { std::unique_lock ul(m); if (state != JFJochState::Idle) throw JFJochException(JFJochExceptionCategory::WrongDAQState,"Must be idle to take pedestal"); TakePedestalInternalAll(ul); } void JFJochStateMachine::Trigger() { std::unique_lock ul(m); if (state == JFJochState::Measuring) services.Trigger(); } void JFJochStateMachine::Start(const JFJochProtoBuf::DatasetSettings& settings) { std::unique_lock ul(m); if (state != JFJochState::Idle) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Must be idle to start measurement"); if (measurement.valid()) measurement.get(); // In case measurement was running - clear thread auto mod_settings = settings; SetDatasetDefaults(mod_settings); experiment.LoadDatasetSettings(mod_settings); ClearAndSetMeasurementStatistics(); cancel_sequence = false; experiment.StorageCellStart(16 - experiment.GetStorageCellNumber()); try { state = JFJochState::Busy; services.SetDataProcessingSettings(GetDataProcessingSettings()); services.Start(experiment, *calibration); state = JFJochState::Measuring; measurement = std::async(std::launch::async, &JFJochStateMachine::WaitTillMeasurementDone, this); } catch (...) { state = JFJochState::Error; services.Abort(); throw; } } void JFJochStateMachine::SetDatasetDefaults(JFJochProtoBuf::DatasetSettings &settings) { if (settings.detector_distance_mm() <= 0) settings.set_detector_distance_mm(100); if (settings.ntrigger() <= 0) settings.set_ntrigger(1); } void JFJochStateMachine::Stop() { std::unique_lock ul(m); if (state == JFJochState::Pedestal) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Cannot use the function during pedestal collection"); c.wait(ul, [&] { return state != JFJochState::Measuring; }); if (!measurement.valid()) return; // This is for unlikely condition of two parallel stops else measurement.get(); switch (state) { case JFJochState::Inactive: throw JFJochException(JFJochExceptionCategory::WrongDAQState,"Not initialized"); case JFJochState::Error: throw JFJochException(JFJochExceptionCategory::WrongDAQState,"Detector in error state"); case JFJochState::Measuring: case JFJochState::Busy: case JFJochState::Pedestal: throw JFJochException(JFJochExceptionCategory::WrongDAQState,"Detector in not expected state to end measurment"); case JFJochState::Idle: break; } } void JFJochStateMachine::WaitTillMeasurementDone() { try { auto tmp_output = services.Stop(*calibration); SetFullMeasurementOutput(tmp_output); { std::unique_lock ul(m); state = JFJochState::Idle; } } catch (...) { std::unique_lock ul(m); state = JFJochState::Error; } c.notify_all(); } void JFJochStateMachine::Abort() { // This is inconsistency in naming - need to solve later std::unique_lock ul(m); if ((state == JFJochState::Pedestal) || (state == JFJochState::Measuring)) { services.Abort(); cancel_sequence = true; } } void JFJochStateMachine::Cancel() { // This is inconsistency in naming - need to solve later std::unique_lock ul(m); if ((state == JFJochState::Pedestal) || (state == JFJochState::Measuring)) { services.Cancel(); cancel_sequence = true; } } void JFJochStateMachine::DebugOnly_SetState(JFJochState in_state) { std::unique_lock ul(m); state = in_state; } void JFJochStateMachine::Deactivate() { std::unique_lock ul(m); try { if (measurement.valid()) measurement.get(); services.Off(); state = JFJochState::Inactive; } catch (...) { state = JFJochState::Error; throw; } } JFJochStateMachine::~JFJochStateMachine() { try { if (measurement.valid()) measurement.get(); } catch (...) {} } JFJochProtoBuf::BrokerFullStatus JFJochStateMachine::GetFullMeasurementOutput() const { std::unique_lock ul(last_receiver_output_mutex); return last_receiver_output; } void JFJochStateMachine::SetFullMeasurementOutput(JFJochProtoBuf::BrokerFullStatus &output) { std::unique_lock ul(last_receiver_output_mutex); last_receiver_output = output; auto tmp = JFJochProtoBuf::MeasurementStatistics(); // reset last measurement statistics tmp.set_file_prefix(experiment.GetFilePrefix()); tmp.set_detector_width(experiment.GetXPixelsNum()); tmp.set_detector_height(experiment.GetYPixelsNum()); tmp.set_detector_pixel_depth(experiment.GetPixelDepth()); if (last_receiver_output.has_receiver()) { tmp.set_compression_ratio(output.receiver().compressed_ratio()); tmp.set_collection_efficiency(output.receiver().efficiency()); tmp.set_images_collected(output.receiver().images_sent()); tmp.set_cancelled(output.receiver().cancelled()); tmp.set_max_image_number_sent(output.receiver().max_image_number_sent()); tmp.set_max_receive_delay(output.receiver().max_receive_delay()); if (output.receiver().has_indexing_rate()) tmp.set_indexing_rate(output.receiver().indexing_rate()); if (output.receiver().has_bkg_estimate()) tmp.set_bkg_estimate(output.receiver().bkg_estimate()); } if (last_receiver_output.writer_size() > 0) { double writer_perf = 0.0; int64_t images_written = 0; for (const auto &i: output.writer()) { writer_perf += i.performance_mbs(); images_written += i.nimages(); for (const auto &f: i.file_statistics()) *tmp.add_file_statistics() = f; } tmp.set_writer_performance_mbs(writer_perf); tmp.set_images_written(images_written); } measurement_statistics = tmp; } void JFJochStateMachine::ClearAndSetMeasurementStatistics() { std::unique_lock ul(last_receiver_output_mutex); measurement_statistics = JFJochProtoBuf::MeasurementStatistics(); measurement_statistics->set_file_prefix(experiment.GetFilePrefix()); measurement_statistics->set_detector_height(experiment.GetXPixelsNum()); measurement_statistics->set_detector_width(experiment.GetYPixelsNum()); measurement_statistics->set_detector_pixel_depth(experiment.GetPixelDepth()); } void JFJochStateMachine::ClearMeasurementStatistics() { std::unique_lock ul(last_receiver_output_mutex); measurement_statistics.reset(); } std::optional JFJochStateMachine::GetMeasurementStatistics() const { std::unique_lock ul(last_receiver_output_mutex); return measurement_statistics; } JFCalibration JFJochStateMachine::GetCalibration() const { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); return *calibration; } void JFJochStateMachine::LoadMask(const std::vector &vec, uint32_t bit) { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); if (state != JFJochState::Idle) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Cannot load mask if detector is not idle"); calibration->LoadMask(experiment, vec, bit); } JFJochProtoBuf::JFCalibrationStatistics JFJochStateMachine::GetCalibrationStatistics() const { std::unique_lock ul(calibration_statistics_mutex); return calibration_statistics; } void JFJochStateMachine::SetCalibrationStatistics(const JFJochProtoBuf::JFCalibrationStatistics &input) { std::unique_lock ul(calibration_statistics_mutex); calibration_statistics = input; } JFJochProtoBuf::DetectorSettings JFJochStateMachine::GetDetectorSettings() const { std::unique_lock ul(m); return experiment.GetDetectorSettings(); } void JFJochStateMachine::SetDetectorSettings(const JFJochProtoBuf::DetectorSettings &settings) { std::unique_lock ul(m); switch (state) { case JFJochState::Inactive: case JFJochState::Error: experiment.LoadDetectorSettings(settings); break; case JFJochState::Idle: experiment.LoadDetectorSettings(settings); TakePedestalInternalAll(ul); break; case JFJochState::Measuring: case JFJochState::Busy: case JFJochState::Pedestal: throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Cannot change detector set during data collection"); } } DiffractionExperiment &JFJochStateMachine::NotThreadSafe_Experiment() { return experiment; } JFJochProtoBuf::Image JFJochStateMachine::GetNeXusMask() const { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); JFJochProtoBuf::Image ret; auto mask = calibration->CalculateNexusMask(experiment); ret.set_width(experiment.GetXPixelsNum()); ret.set_height(experiment.GetYPixelsNum()); ret.set_pixel_depth(4); *ret.mutable_data() = {mask.begin(), mask.end()}; return ret; } JFJochProtoBuf::Image JFJochStateMachine::GetPedestalG0() const { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); JFJochProtoBuf::Image ret; auto pedestal = calibration->GetPedestal(0); ret.set_width(experiment.GetXPixelsNum()); ret.set_height(experiment.GetYPixelsNum()); ret.set_pixel_depth(2); *ret.mutable_data() = {pedestal.begin(), pedestal.end()}; return ret; } JFJochProtoBuf::Image JFJochStateMachine::GetPedestalG1() const { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); JFJochProtoBuf::Image ret; auto pedestal = calibration->GetPedestal(1); ret.set_width(experiment.GetXPixelsNum()); ret.set_height(experiment.GetYPixelsNum()); ret.set_pixel_depth(2); *ret.mutable_data() = {pedestal.begin(), pedestal.end()}; return ret; } JFJochProtoBuf::Image JFJochStateMachine::GetPedestalG2() const { std::unique_lock ul(m); if (state == JFJochState::Inactive) throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Detector not calibrated"); JFJochProtoBuf::Image ret; auto pedestal = calibration->GetPedestal(2); ret.set_width(experiment.GetXPixelsNum()); ret.set_height(experiment.GetYPixelsNum()); ret.set_pixel_depth(2); *ret.mutable_data() = {pedestal.begin(), pedestal.end()}; return ret; } JFJochProtoBuf::BrokerStatus JFJochStateMachine::GetStatus() const { JFJochProtoBuf::BrokerStatus ret; switch (state) { case JFJochState::Inactive: ret.set_broker_state(JFJochProtoBuf::NOT_INITIALIZED); break; case JFJochState::Idle: ret.set_broker_state(JFJochProtoBuf::IDLE); break; case JFJochState::Measuring: ret.set_broker_state(JFJochProtoBuf::DATA_COLLECTION); break; case JFJochState::Error: ret.set_broker_state(JFJochProtoBuf::ERROR); break; case JFJochState::Busy: ret.set_broker_state(JFJochProtoBuf::BUSY); break; case JFJochState::Pedestal: ret.set_broker_state(JFJochProtoBuf::PEDESTAL); break; } try { auto rcv_status = services.GetReceiverStatus(); if (rcv_status.has_progress()) ret.set_progress(rcv_status.progress()); if (rcv_status.has_indexing_rate()) ret.set_indexing_rate(rcv_status.indexing_rate()); if (rcv_status.has_send_buffers_avail()) ret.set_receiver_send_buffers_avail(rcv_status.send_buffers_avail()); } catch (JFJochException &e) {} // ignore exception in getting receiver status (don't really care, e.g. if receiver is down) return ret; } JFJochProtoBuf::Plot JFJochStateMachine::GetPlots(const JFJochProtoBuf::PlotRequest &request) const { return services.GetPlots(request); } JFJochProtoBuf::RadialIntegrationProfiles JFJochStateMachine::GetRadialIntegrationProfiles() const { return services.GetRadialIntegrationProfiles(); } void JFJochStateMachine::SetDataProcessingSettings(const JFJochProtoBuf::DataProcessingSettings &settings) { std::unique_lock ul(data_processing_settings_mutex); DiffractionExperiment::CheckDataProcessingSettings(settings); data_processing_settings = settings; services.SetDataProcessingSettings(data_processing_settings); } JFJochProtoBuf::DataProcessingSettings JFJochStateMachine::GetDataProcessingSettings() const { std::unique_lock ul(data_processing_settings_mutex); return data_processing_settings; } JFJochState JFJochStateMachine::GetState() const { return state; } void JFJochStateMachine::AddDetectorSetup(const DetectorSetup &setup) { std::unique_lock ul(m); if (detector_setup.empty()) { experiment.Detector(setup); gain_calibration = setup.GetGainCalibration(); current_detector_setup = 0; } detector_setup.emplace_back(setup); } JFJochProtoBuf::DetectorList JFJochStateMachine::GetDetectorsList() { std::unique_lock ul(m); JFJochProtoBuf::DetectorList ret; for (int i = 0; i < detector_setup.size(); i++) { auto tmp = ret.add_detector(); tmp->set_description(detector_setup[i].GetDescription()); tmp->set_nmodules(detector_setup[i].GetModulesNum()); tmp->set_id(i); } ret.set_current_id(current_detector_setup); ret.set_current_description(experiment.GetDetectorDescription()); return ret; } void JFJochStateMachine::SelectDetector(int64_t id) { std::unique_lock ul(m); if ((id < 0) || (id >= detector_setup.size())) throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Detector doesn't exist"); switch (state) { case JFJochState::Inactive: case JFJochState::Error: case JFJochState::Idle: try { experiment.Detector(detector_setup[id]); gain_calibration = detector_setup[id].GetGainCalibration(); state = JFJochState::Inactive; current_detector_setup = id; } catch (JFJochException &e) { logger.ErrorException(e); state = JFJochState::Inactive; } break; case JFJochState::Measuring: case JFJochState::Busy: case JFJochState::Pedestal: throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Cannot change detector during data collection"); } }