Commit 56e707dd authored by Emmanuel Papillon's avatar Emmanuel Papillon

Merge branch 'cdte-high-voltage' into 'master'

Cdte high voltage

See merge request !10
parents 17ba1341 5a624480
Pipeline #37561 passed with stages
in 17 minutes and 7 seconds
......@@ -122,6 +122,10 @@ class LIBEIGER Camera : public HwMaxImageSizeCallbackGen, public EventCallbackGe
void getTemperature(double&);
void getHumidity(double&);
void getHighVoltageState(std::string&);
void getHighVoltageMeasured(double&);
void getHighVoltageTarget(double&);
void resetHighVoltage();
void setCountrateCorrection(bool);
void getCountrateCorrection(bool&);
......@@ -131,6 +135,8 @@ class LIBEIGER Camera : public HwMaxImageSizeCallbackGen, public EventCallbackGe
void getAutoSummation(bool&);
void setEfficiencyCorrection(bool);
void getEfficiencyCorrection(bool& value);
void setRetrigger(bool);
void getRetrigger(bool &value);
void setPixelMask(bool);
void getPixelMask(bool&);
void setThresholdEnergy(double);
......@@ -178,7 +184,7 @@ class LIBEIGER Camera : public HwMaxImageSizeCallbackGen, public EventCallbackGe
Camera::Status _getStatus();
void _synchronize(); /// Used during plug-in initialization
void _trigger_finished(bool);
void _trigger_finished(bool ok, bool do_disarm);
void _initialization_finished(bool ok);
void _updateImageSize();
......
......@@ -67,11 +67,15 @@ namespace eigerapi
void handle_result(CURLcode result);
void _status_changed();
bool check_http_response(const char *ptr, size_t size);
static void *_callback_thread_runFunc(void *data);
CURL* m_handle;
Status m_status;
std::string m_error_code;
int m_http_code;
std::string m_http_msg;
// Synchro
mutable pthread_mutex_t m_lock;
mutable pthread_cond_t m_cond;
......
......@@ -137,9 +137,12 @@ namespace eigerapi
typedef std::shared_ptr<Transfer> TransferReq;
enum COMMAND_NAME {INITIALIZE,ARM, DISARM,TRIGGER,CANCEL,ABORT,
FILEWRITER_CLEAR};
FILEWRITER_CLEAR, HV_RESET};
enum PARAM_NAME {TEMP,
HUMIDITY,
HVSTATE,
HVMEASURED,
HVTARGET,
DETECTOR_STATUS,
PIXELDEPTH,
X_PIXEL_SIZE,
......@@ -149,14 +152,15 @@ namespace eigerapi
DESCRIPTION,
DETECTOR_NUMBER,
DETECTOR_READOUT_TIME,
DATA_COLLECTION_DATE,
SOFTWARE_VERSION,
DATA_COLLECTION_DATE,
SOFTWARE_VERSION,
EXPOSURE,
FRAME_TIME,
TRIGGER_MODE,
COUNTRATE_CORRECTION,
FLATFIELD_CORRECTION,
EFFICIENCY_CORRECTION,
RETRIGGER,
PIXEL_MASK,
THRESHOLD_ENERGY,
VIRTUAL_PIXEL_CORRECTION,
......
......@@ -28,6 +28,8 @@
#include "eigerapi/EigerDefines.h"
#include "AutoMutex.h"
#include <regex>
using namespace eigerapi;
// Constant
......@@ -50,6 +52,7 @@ struct CURL_INIT
CurlLoop::FutureRequest::FutureRequest(const std::string& url) :
m_status(IDLE),
m_http_code(0),
m_cbk(NULL),
m_url(url)
{
......@@ -78,7 +81,7 @@ CurlLoop::FutureRequest::~FutureRequest()
inline void CurlLoop::FutureRequest::handle_result(CURLcode result)
{
Lock lock(&m_lock);
if(m_status != FutureRequest::CANCEL)
if(m_status == FutureRequest::RUNNING)
{
switch(result)
{
......@@ -97,6 +100,24 @@ inline void CurlLoop::FutureRequest::handle_result(CURLcode result)
_request_finished();
}
bool CurlLoop::FutureRequest::check_http_response(const char *ptr, size_t size)
{
static const std::regex re("([1-5][0-9]{2}) (.+)");
std::cmatch m;
if(!std::regex_match(ptr, ptr + size, m, re))
return false;
Lock lock(&m_lock);
m_http_code = std::stoi(m[1].str());
m_http_msg = m[2].str();
std::cout << "HTTP [" << m_url << "]: " << m[0].str() << std::endl;
if((m_status == FutureRequest::RUNNING) && (m_http_code >= 400)) {
m_status = FutureRequest::ERROR;
m_error_code = m_http_msg;
}
return true;
}
void CurlLoop::FutureRequest::wait(double timeout,bool lock_flag) const
{
Lock lock(&m_lock,lock_flag);
......
......@@ -41,6 +41,7 @@ typedef Requests::CurlReq CurlReq;
static const char* CSTR_EIGERCONFIG = "config";
static const char* CSTR_EIGERSTATUS = "status";
static const char* CSTR_EIGERSTATUS_BOARD = "status/board_000";
static const char* CSTR_EIGERSTATUS_HV = "status/high_voltage";
static const char* CSTR_EIGERCOMMAND = "command";
static const char* CSTR_EIGERFILES = "files";
static const char* CSTR_SUBSYSTEMFILEWRITER = "filewriter";
......@@ -83,6 +84,7 @@ static CommandIndex CommandsDescription[] = {
{Requests::CANCEL, {"cancel",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERCOMMAND}},
{Requests::ABORT, {"abort",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERCOMMAND}},
{Requests::FILEWRITER_CLEAR, {"clear",CSTR_SUBSYSTEMFILEWRITER,CSTR_EIGERCOMMAND}},
{Requests::HV_RESET, {"hv_reset",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERCOMMAND}},
};
const char* get_cmd_name(Requests::COMMAND_NAME cmd_name)
......@@ -105,6 +107,9 @@ ParamIndex ParamDescription[] = {
// Detector Read only values
{Requests::TEMP, {"th0_temp",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS_BOARD}},
{Requests::HUMIDITY, {"th0_humidity",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS_BOARD}},
{Requests::HVSTATE, {"state",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS_HV}},
{Requests::HVMEASURED, {"measured",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS_HV}},
{Requests::HVTARGET, {"target",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS_HV}},
{Requests::DETECTOR_STATUS, {"state",CSTR_SUBSYSTEMDETECTOR,CSTR_EIGERSTATUS}},
{Requests::PIXELDEPTH, {"bit_depth_readout"}},
{Requests::X_PIXEL_SIZE, {"x_pixel_size"}},
......@@ -131,6 +136,7 @@ ParamIndex ParamDescription[] = {
{Requests::NIMAGES, {"nimages"}},
{Requests::NTRIGGER, {"ntrigger"}},
{Requests::AUTO_SUMMATION, {"auto_summation"}},
{Requests::RETRIGGER, {"retrigger"}},
// Filewriter settings
{Requests::FILEWRITER_MODE, {"mode",CSTR_SUBSYSTEMFILEWRITER}},
......@@ -405,7 +411,13 @@ void Requests::Command::_fill_request()
size_t Requests::Command::_write_callback(char *ptr,size_t size,
size_t nmemb,Requests::Command *cmd)
{
int size_to_copy = std::min(size * nmemb,sizeof(m_data));
size_t len = size * nmemb;
if (cmd->check_http_response(ptr, len))
return len;
int size_to_copy = std::min(len, sizeof(m_data) - 1);
if (size_to_copy < len)
std::cout << "Requests::Command: Warning: unexpected (long) message: "
<< std::string(ptr, len) << std::endl;
memcpy(cmd->m_data,ptr,size_to_copy);
cmd->m_data[size_to_copy] = '\0';
return size_to_copy;
......
......@@ -81,6 +81,10 @@ namespace Eiger
void getTemperature(double& /Out/);
void getHumidity(double& /Out/);
void getHighVoltageState(std::string& /Out/);
void getHighVoltageMeasured(double& /Out/);
void getHighVoltageTarget(double& /Out/);
void resetHighVoltage();
void setCountrateCorrection(const bool);
void getCountrateCorrection(bool& /Out/);
......@@ -90,6 +94,8 @@ namespace Eiger
void getAutoSummation(bool& /Out/);
void setEfficiencyCorrection(const bool);
void getEfficiencyCorrection(bool& value /Out/);
void setRetrigger(bool);
void getRetrigger(bool& /Out/);
void setPixelMask(const bool);
void getPixelMask(bool& /Out/);
void setThresholdEnergy(const double);
......
......@@ -61,7 +61,10 @@ class Camera::TriggerCallback : public Callback
{
DEB_CLASS_NAMESPC(DebModCamera, "Camera::TriggerCallback", "Eiger");
public:
TriggerCallback(Camera& cam) : m_cam(cam) {}
TriggerCallback(Camera& cam, bool disarm, double duration)
: m_cam(cam), m_disarm(disarm), m_duration(duration),
m_start_ts(Timestamp::now())
{}
void status_changed(CurlLoop::FutureRequest::Status status,
std::string error)
......@@ -69,12 +72,27 @@ public:
DEB_MEMBER_FUNCT();
DEB_PARAM() << DEB_VAR2(status, error);
bool ok = (status == CurlLoop::FutureRequest::OK);
if (!ok) {
// the HTTP server can close the connection without completing
// trigger commands longer than 5 min. a retry (by libcurl) fails
const double timeout_limit = 5 * 60;
bool long_trigger = (m_duration > timeout_limit - 1);
// if the command lasted the expected duration, ignore the error
double elapsed = Timestamp::now() - m_start_ts;
if (long_trigger && (fabs(elapsed - m_duration) < 2)) {
DEB_ALWAYS() << "Ignoring trigger command error";
ok = true;
}
}
if (!ok)
DEB_ERROR() << DEB_VAR1(error);
m_cam._trigger_finished(ok);
m_cam._trigger_finished(ok, m_disarm);
}
private:
Camera& m_cam;
bool m_disarm;
double m_duration;
Timestamp m_start_ts;
};
class Camera::InitCallback : public Callback
......@@ -275,12 +293,12 @@ void Camera::startAcq()
{
CommandReq trigger = m_requests->get_command(Requests::TRIGGER);
m_trigger_state = RUNNING;
m_frames_triggered += m_nb_triggers;
m_frames_triggered += m_nb_images;
bool disarm_at_end = (m_frames_triggered == m_nb_frames);
DEB_TRACE() << "Trigger start: " << DEB_VAR1(disarm_at_end);
double duration = m_nb_images * m_frame_time;
AutoMutexUnlock u(lock);
CallbackPtr cbk(new TriggerCallback(*this));
CallbackPtr cbk(new TriggerCallback(*this, disarm_at_end, duration));
trigger->register_callback(cbk, disarm_at_end);
}
......@@ -600,7 +618,7 @@ Camera::Status Camera::_getStatus()
status = Fault;
else if(m_trigger_state == RUNNING)
status = Exposure;
else if(m_armed)
else if(m_armed && (m_frames_triggered == 0))
status = Armed;
else if(m_initialize_state == RUNNING)
status = Initializing;
......@@ -719,7 +737,13 @@ void Camera::_synchronize()
param = Requests::FRAME_TIME;
m_min_frame_time = synchro[param]->get_min().data.double_val;
param = Requests::DETECTOR_READOUT_TIME;
m_readout_time = synchro[param]->get_min().data.double_val;
ParamReq req = synchro[param];
Requests::Param::Value value = req->get_min();
m_readout_time = value.data.double_val;
if (m_readout_time <= 0) {
value = req->get();
m_readout_time = value.data.double_val;
}
} catch (const EigerException& e) {
HANDLE_EIGERERROR(synchro[param], e);
}
......@@ -750,21 +774,20 @@ void Camera::_updateImageSize()
/*----------------------------------------------------------------------------
This method is called when the trigger is finished
----------------------------------------------------------------------------*/
void Camera::_trigger_finished(bool ok)
void Camera::_trigger_finished(bool ok, bool do_disarm)
{
DEB_MEMBER_FUNCT();
DEB_PARAM() << DEB_VAR1(ok);
AutoMutex lock(m_cond.mutex());
DEB_TRACE() << "Trigger end";
if(!ok) {
DEB_ERROR() << "Error in trigger command";
} else if(m_frames_triggered == m_nb_frames) {
AutoMutexUnlock u(lock);
} else if(do_disarm) {
try { disarm(); }
catch (...) { ok = false; }
}
AutoMutex lock(m_cond.mutex());
m_trigger_state = ok ? IDLE : ERROR;
}
......@@ -825,6 +848,37 @@ void Camera::getHumidity(double &humidity)
}
//-----------------------------------------------------------------------------
/// Returns the high voltage state
/*!
@return state string
*/
//-----------------------------------------------------------------------------
void Camera::getHighVoltageState(std::string &hvstate)
{
DEB_MEMBER_FUNCT();
getParam(Requests::HVSTATE,hvstate);
}
void Camera::resetHighVoltage()
{
DEB_MEMBER_FUNCT();
sendCommand(Requests::HV_RESET);
DEB_TRACE() << "reset HighVoltage";
}
void Camera::getHighVoltageMeasured(double &hvmeas)
{
DEB_MEMBER_FUNCT();
getParam(Requests::HVMEASURED, hvmeas);
}
void Camera::getHighVoltageTarget(double &hvtarget)
{
DEB_MEMBER_FUNCT();
getParam(Requests::HVTARGET, hvtarget);
}
//-----------------------------------------------------------------------------
/// Count rate correction setter
//-----------------------------------------------------------------------------
......@@ -864,6 +918,26 @@ void Camera::getFlatfieldCorrection(bool& value) ///< [out] true:enabled, false:
getParam(Requests::FLATFIELD_CORRECTION,value);
}
//-----------------------------------------------------------------------------
/// Retrigger mode setter
//-----------------------------------------------------------------------------
void Camera::setRetrigger(bool value) ///< [in] true:enabled, false:disabled
{
DEB_MEMBER_FUNCT();
setParam(Requests::RETRIGGER,value);
}
//-----------------------------------------------------------------------------
/// Retrigger getter
//-----------------------------------------------------------------------------
void Camera::getRetrigger(bool& value) ///< [out] true:enabled, false:disabled
{
DEB_MEMBER_FUNCT();
getParam(Requests::RETRIGGER,value);
}
//----------------------------------------------------------------------------
// Auto Summation setter
//----------------------------------------------------------------------------
......@@ -885,6 +959,8 @@ void Camera::getAutoSummation(bool& value)
getParam(Requests::AUTO_SUMMATION,value);
DEB_RETURN() << DEB_VAR1(value);
}
//-----------------------------------------------------------------------------
/// PixelMask setter
//-----------------------------------------------------------------------------
......
......@@ -131,18 +131,17 @@ void Interface::prepareAcq()
void Interface::startAcq()
{
DEB_MEMBER_FUNCT();
TrigMode trig_mode;
m_cam.getTrigMode(trig_mode);
int nb_trig_frames;
m_cam.getNbTriggeredFrames(nb_trig_frames);
// start data retrieval subsystems only in first call
if (getNbHwAcquiredFrames() == 0) {
if ((trig_mode != IntTrigMult) || (nb_trig_frames == 0)) {
// either we use eiger saving or the raw stream
if(m_saving->isActive())
m_saving->start();
else
m_stream->start();
} else {
TrigMode trig_mode;
m_cam.getTrigMode(trig_mode);
if (trig_mode != IntTrigMult)
DEB_WARNING() << "Unexpected start";
}
m_cam.startAcq();
......@@ -171,17 +170,11 @@ void Interface::getStatus(StatusType& status)
Eiger_status = m_cam.getStatus();
switch (Eiger_status)
{
case Camera::Armed:
case Camera::Ready:
{
bool mult_trig_in_progress = false;
TrigMode trig_mode;
m_cam.getTrigMode(trig_mode);
if ((Eiger_status == Camera::Armed) &&
((trig_mode == IntTrig) || (trig_mode == IntTrigMult))) {
status.set(HwInterface::StatusType::Ready);
break;
}
bool mult_trig_in_progress = false;
if (trig_mode == IntTrigMult) {
int tot_nb_frames, nb_trig_frames;
m_cam.getNbFrames(tot_nb_frames);
......@@ -214,6 +207,10 @@ void Interface::getStatus(StatusType& status)
status.set(HwInterface::StatusType::Exposure);
break;
case Camera::Armed:
status.set(HwInterface::StatusType::Ready);
break;
case Camera::Fault:
status.set(HwInterface::StatusType::Fault);
break;
......
......@@ -299,7 +299,11 @@ void Stream::_ZmqThread::_run_sequence()
bool continue_flag = true;
while(continue_flag) { // reading loop
DEB_TRACE() << "Enter poll";
zmq_poll(items,2,-1);
long timeout_ms = m_stopped ? 2000 : -1;
if (zmq_poll(items,2,timeout_ms) <= 0) {
DEB_ERROR() << "No (end) message received after Abort";
break;
}
DEB_TRACE() << "Exit poll";
if(items[0].revents & ZMQ_POLLIN) { // reading synchro pipe
......
......@@ -81,12 +81,14 @@ class Eiger(PyTango.Device_4Impl):
'OFF':False}
self.__VirtualPixelCorrection = {'ON':True,
'OFF':False}
self.__Retrigger = {'ON':True,
'OFF':False}
self.__PixelMask = {'ON':True,
'OFF':False}
self.__CompressionType = {'NONE': EigerAcq.Camera.NoCompression,
'LZ4': EigerAcq.Camera.LZ4,
'BSLZ4': EigerAcq.Camera.BSLZ4}
self.__Status = {'INITIALIZING': EigerAcq.Camera.Initializing,
self.__PluginStatus = {'INITIALIZING': EigerAcq.Camera.Initializing,
'READY': EigerAcq.Camera.Ready,
'ARMED': EigerAcq.Camera.Armed,
'EXPOSURE': EigerAcq.Camera.Exposure,
......@@ -123,7 +125,8 @@ class Eiger(PyTango.Device_4Impl):
#==================================================================
def __getattr__(self,name) :
if name == 'read_plugin_status':
name = 'read_status'
func2call = getattr(_EigerCamera, "getStatus")
return AttrHelper.CallableReadEnum(self.__PluginStatus, func2call)
return AttrHelper.get_attr_4u(self,name,_EigerCamera)
#==================================================================
......@@ -180,6 +183,13 @@ class Eiger(PyTango.Device_4Impl):
stream_stats.ave_time(),
stream_stats.ave_speed()]
#----------------------------------------------------------------------------
# reset high voltage
#----------------------------------------------------------------------------
@Core.DEB_MEMBER_FUNCT
def resetHighVoltage(self):
_EigerCamera.resetHighVoltage()
#==================================================================
#
# EigerClass class definition
......@@ -220,6 +230,9 @@ class EigerClass(PyTango.DeviceClass):
'latchStreamStatistics':
[[PyTango.DevBoolean, "Reset statistics"],
[PyTango.DevVarDoubleArray, "[<ave_size>, <ave_time>, <ave_speed>]"]],
'resetHighVoltage':
[[PyTango.DevVoid, ""],
[PyTango.DevVoid, ""]],
}
......@@ -237,6 +250,18 @@ class EigerClass(PyTango.DeviceClass):
[[PyTango.DevFloat,
PyTango.SCALAR,
PyTango.READ]],
'high_voltage_state':
[[PyTango.DevString,
PyTango.SCALAR,
PyTango.READ]],
'high_voltage_measured':
[[PyTango.DevFloat,
PyTango.SCALAR,
PyTango.READ]],
'high_voltage_target':
[[PyTango.DevFloat,
PyTango.SCALAR,
PyTango.READ]],
'countrate_correction':
[[PyTango.DevString,
PyTango.SCALAR,
......@@ -257,6 +282,10 @@ class EigerClass(PyTango.DeviceClass):
[[PyTango.DevString,
PyTango.SCALAR,
PyTango.READ_WRITE]],
'retrigger':
[[PyTango.DevString,
PyTango.SCALAR,
PyTango.READ_WRITE]],
'pixel_mask':
[[PyTango.DevString,
PyTango.SCALAR,
......
from Lima import Core, Eiger
import time
import sys
import getopt
import numpy as np
from collections import namedtuple
from contextlib import contextmanager
from threading import Condition, Event
AcqPars = namedtuple('AcqPars',
['trig_mode', 'nb_frames', 'expo_time', 'lat_time'])
class TestIntTrig:
class Cb(Core.CtControl.ImageStatusCallback):
def __init__(self, frame_ts, end, acq_pars):
super().__init__()
self.last_acquired = -1
self.frame_ts = frame_ts
self.end = end
self.acq_pars = acq_pars
def imageStatusChanged(self, status):
if status.LastImageAcquired == self.last_acquired:
return
self.frame_ts.append(time.time())
self.last_acquired = status.LastImageAcquired
if self.last_acquired == self.acq_pars.nb_frames - 1:
self.end.set()
def __init__(self, hw_inter):
self.hw_inter = hw_inter
self.ct = Core.CtControl(self.hw_inter)
self.acq = self.ct.acquisition()
self.sync = self.hw_inter.getHwCtrlObj(Core.HwCap.Sync)
def run(self, acq_pars):
if acq_pars.trig_mode == Core.IntTrig:
self.runIntTrig(acq_pars)
else:
self.runIntTrigMult(acq_pars)
def prepare(self, acq_pars):
self.acq.setTriggerMode(acq_pars.trig_mode)
self.acq.setAcqNbFrames(acq_pars.nb_frames)
self.acq.setAcqExpoTime(acq_pars.expo_time)
self.acq.setLatencyTime(acq_pars.lat_time)
self.ct.prepareAcq()
ranges = self.sync.getValidRanges()
print("Valid ranges: ")
print(f" min_exp_time={ranges.min_exp_time:.7f},"
f" max_exp_time={ranges.max_exp_time:.7f}")
print(f" min_lat_time={ranges.min_lat_time:.7f},"
f" max_lat_time={ranges.max_lat_time:.7f}")
def runIntTrig(self, acq_pars):
frame_ts = []
end = Event()
cb = self.Cb(frame_ts, end, acq_pars)
cb.setRatePolicy(cb.RateAllFrames)
self.ct.registerImageStatusCallback(cb)
self.prepare(acq_pars)
print("Starting acquisition ...")
t0 = time.time()
self.ct.startAcq()
end.wait()
trigger_delay = time.time() - t0
while self.ct.getStatus().AcquisitionStatus != Core.AcqReady:
pass
acq_delay = time.time() - t0
if len(frame_ts) != acq_pars.nb_frames:
print(f"Missing triggers: {len(frame_ts)}, expected {acq_pars.nb_frames}")
bin_size = 5e-3
bins = self.splitTimeBins(frame_ts, acq_pars, bin_size)
print(f"Frame delay bins: {len(bins)} ({bin_size} size)")
for (ave, std), b in sorted(bins):
print(f" n={len(b):02d}, ave={ave:.6f}, std={std:.6f}")
first_trigger_delay = frame_ts[0] - t0
point_time = acq_pars.expo_time + acq_pars.lat_time
first_trigger_overhead = first_trigger_delay - point_time
acq_time = acq_pars.nb_frames * point_time
overhead = trigger_delay - acq_time
print(f"First trigger delay: {first_trigger_delay:.3f} "
f"({point_time:.3f} + {first_trigger_overhead:.3f})")
print(f"Trigger delay: {trigger_delay:.3f} "
f"({acq_time:.3f} + {overhead:.3f})")
print(f"Acquisition finished after {acq_delay:.3f} sec "
f"({trigger_delay:.3f} + {acq_delay - trigger_delay:.3f})")
def splitTimeBins(self, ts, acq_pars, bin_size):
deltas = [ts[i + 1] - ts[i] for i in range(len(ts) - 1)]
bins = []
for t in deltas:
for b in bins:
if abs(t - np.average(b)) <= bin_size:
b.append(t)
break
else:
bins.append([t])
return [((np.average(b), np.std(b)), b) for b in bins]
def runIntTrigMult(self, acq_pars):
self.prepare(acq_pars)
print("Starting acquisition ...")
trigger_delay = []
for i in range(nb_frames):
print(f"Sending start #{i}")
self.ct.startAcq()
t0 = time.time()
while self.hw_inter.getStatus().det != Core.DetIdle:
pass
trigger_delay.append(time.time() - t0)
t0 = time.time()