gondola_core/tof/
settings.rs

1//! Aggregate settings for the TOF system
2//!
3//! Control the settings for the C&C server
4//! as well as the liftof-clients on the RBs
5//!
6//! Different sections might represent different
7//! threads/aspects of the code
8//!
9// This file is part of gaps-online-software and published 
10// under the GPLv3 license
11
12use crate::prelude::*;
13
14/// Define which entitiy will configure another entity
15#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
16pub enum ParameterSetStrategy {
17  /// Configuration through the TOF cpu
18  ControlServer,
19  /// Configuration through the relevant board itself
20  Board
21}
22
23/// Configure the trigger
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25pub struct MTBSettings {
26  /// Select the trigger type for this run
27  pub trigger_type           : TriggerType,
28  /// Select the prescale factor for a run. The
29  /// prescale factor is between 0 (no events)
30  /// and 1.0 (all events). E.g. 0.1 means allow 
31  /// only 10% of the events
32  /// THIS DOES NOT APPLY TO THE GAPS OR POISSON 
33  /// TRIGGER!
34  pub trigger_prescale               : f32,
35  /// Set to true if we want a combo trigger run
36  pub use_combo_trigger              : bool,
37  /// Set the global trigger type. This has to be less 
38  /// strict than the trigger type   
39  pub global_trigger_type            : TriggerType,
40  /// Set the gloabl trigger prescale
41  pub global_trigger_prescale        : f32,
42
43  /// in case trigger_type = "Poisson", set rate here
44  pub poisson_trigger_rate           : u32,
45  /// in case trigger_type = "Gaps", set if we want to use 
46  /// beta
47  pub gaps_trigger_use_beta     : bool,
48  /// In case we are running the fixed rate trigger, set the
49  /// desired rate here
50  /// not sure
51  //pub gaps_trigger_inner_thresh : u32,
52  ///// not sure
53  //pub gaps_trigger_outer_thresh : u32, 
54  ///// not sure
55  //pub gaps_trigger_total_thresh : u32, 
56  ///// not sure
57  //pub gaps_trigger_hit_thresh   : u32,
58  /// Enable trace suppression on the MTB. If enabled, 
59  /// only those RB which hits will read out waveforms.
60  /// In case it is disabled, ALL RBs will readout events
61  /// ALL the time. For this, we need also the eventbuilder
62  /// strategy "WaitForNBoards(40)"
63  pub trace_suppression  : bool,
64  /// The number of seconds we want to wait
65  /// without hearing from the MTB before
66  /// we attempt a reconnect
67  pub mtb_timeout_sec    : u64,
68  /// Time in seconds between housekkeping 
69  /// packets
70  pub mtb_moni_interval  : u64,
71  pub rb_int_window      : u8,
72  pub tiu_emulation_mode : bool,
73  pub tiu_ignore_busy    : bool,
74  pub tofbot_webhook     : String,
75  pub hb_send_interval   : u64,
76  /// Instruct the MTB to ignore the tiu 
77  /// busy time and instead impose always 
78  /// the same deadtime of 600mu sec on 
79  /// itself
80  pub use_fixed_deadtime : Option<bool>,
81}
82
83impl MTBSettings {
84  pub fn new() -> Self {
85    Self {
86      trigger_type            : TriggerType::Unknown,
87      trigger_prescale        : 0.0,
88      poisson_trigger_rate    : 0,
89      gaps_trigger_use_beta   : true,
90      trace_suppression       : true,
91      mtb_timeout_sec         : 60,
92      mtb_moni_interval       : 30,
93      rb_int_window           : 1,
94      tiu_emulation_mode      : false,
95      tiu_ignore_busy         : false,
96      tofbot_webhook          : String::from(""),
97      hb_send_interval        : 30,
98      use_combo_trigger       : false,
99      global_trigger_type     : TriggerType::Unknown,
100      global_trigger_prescale : 1.0,
101      use_fixed_deadtime      : None,
102    }
103  }
104
105  /// Emit a config, so that infomraiton can be transported
106  /// over the wire
107  pub fn emit_triggerconfig(&self) -> TriggerConfig {
108    let mut cfg = TriggerConfig::new();
109    // all fields should be active, since the settings file 
110    // contains all fields per definition. We can already 
111    // be future proof and just set all of them
112    cfg.active_fields          = u32::MAX;
113    cfg.gaps_trigger_use_beta  = Some(self.gaps_trigger_use_beta);
114    cfg.prescale               = Some(self.trigger_prescale);
115    cfg.trigger_type           = Some(self.trigger_type);
116    cfg.use_combo_trigger      = Some(self.use_combo_trigger);
117    cfg.combo_trigger_type     = Some(self.global_trigger_type);
118    cfg.combo_trigger_prescale = Some(self.global_trigger_prescale);
119    cfg.trace_suppression      = Some(self.trace_suppression);
120    cfg.mtb_moni_interval      = Some((self.mtb_moni_interval & 0xffff) as u16); 
121    cfg.tiu_ignore_busy        = Some(self.tiu_ignore_busy); 
122    cfg.hb_send_interval       = Some((self.hb_send_interval & 0xffff) as u16); 
123    cfg
124  }
125
126  /// Change seetings accordingly to config 
127  pub fn from_triggerconfig(&mut self, cfg : &TriggerConfig) {
128    if cfg.gaps_trigger_use_beta.is_some() {
129      self.gaps_trigger_use_beta   = cfg.gaps_trigger_use_beta.unwrap() ;
130    }
131    if cfg.prescale.is_some() {
132      self.trigger_prescale        = cfg.prescale.unwrap()              ;
133    }
134    if cfg.trigger_type.is_some() {
135      self.trigger_type            = cfg.trigger_type.unwrap()          ; 
136    }
137    if cfg.use_combo_trigger.is_some() {
138      self.use_combo_trigger       = cfg.use_combo_trigger.unwrap()     ;
139    }
140    if cfg.combo_trigger_type.is_some() {
141      self.global_trigger_type     = cfg.combo_trigger_type.unwrap()    ;
142    }
143    if cfg.combo_trigger_prescale.is_some() {
144      self.global_trigger_prescale = cfg.combo_trigger_prescale.unwrap();
145    }
146    if cfg.trace_suppression.is_some() {
147      self.trace_suppression       = cfg.trace_suppression.unwrap()     ;
148    }
149    if cfg.mtb_moni_interval.is_some() {
150      self.mtb_moni_interval       = cfg.mtb_moni_interval.unwrap() as u64;
151    }
152    if cfg.tiu_ignore_busy.is_some() {
153      self.tiu_ignore_busy         = cfg.tiu_ignore_busy.unwrap()       ;
154    }
155    if cfg.hb_send_interval.is_some() {
156      self.hb_send_interval        = cfg.hb_send_interval.unwrap()  as u64;
157    }
158  }
159}
160
161impl fmt::Display for MTBSettings {
162  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163    let disp = toml::to_string(self).unwrap_or(
164      String::from("-- DESERIALIZATION ERROR! --"));
165    write!(f, "<MTBSettings :\n{}>", disp)
166  }
167}
168
169impl Default for MTBSettings {
170  fn default() -> Self {
171    Self::new()
172  }
173}
174
175#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
176pub struct PreampSettings {
177  /// actually apply the below settings
178  pub set_preamp_voltages    : bool,
179  /// liftof-cc will send commands to set the 
180  /// preamp bias voltages
181  pub set_strategy           : ParameterSetStrategy,
182  /// preamp biases (one set of 16 values per RAT
183  pub rat_preamp_biases      : HashMap<String, [f32;16]>
184}
185
186impl PreampSettings {
187  pub fn new() -> Self {
188    //let default_biases = HashMap::<u8, [f32;16]>::new();
189    let default_biases = HashMap::from([
190      (String::from("RAT01"), [58.0;16]),
191      (String::from("RAT02"), [58.0;16]),
192      (String::from("RAT03"), [58.0;16]),
193      (String::from("RAT04"), [58.0;16]),
194      (String::from("RAT05"), [58.0;16]),
195      (String::from("RAT06"), [58.0;16]),
196      (String::from("RAT07"), [58.0;16]),
197      (String::from("RAT08"), [58.0;16]),
198      (String::from("RAT09"), [58.0;16]),
199      (String::from("RAT10"), [58.0;16]),
200      (String::from("RAT11"), [58.0;16]),
201      (String::from("RAT12"), [58.0;16]),
202      (String::from("RAT13"), [58.0;16]),
203      (String::from("RAT14"), [58.0;16]),
204      (String::from("RAT15"), [58.0;16]),
205      (String::from("RAT16"), [58.0;16]),
206      (String::from("RAT17"), [58.0;16]),
207      (String::from("RAT18"), [58.0;16]),
208      (String::from("RAT19"), [58.0;16]),
209      (String::from("RAT20"), [58.0;16])]);
210
211    Self {
212      set_preamp_voltages    : false,
213      set_strategy           : ParameterSetStrategy::ControlServer,
214      rat_preamp_biases      : default_biases,
215    }
216  }
217
218  #[cfg(feature="database")]
219  pub fn emit_pb_settings_packets(&self, rats : &HashMap<u8,RAT>) -> Vec<TofPacket> {
220    let mut packets = Vec::<TofPacket>::new();
221    for k in rats.keys() {
222      let rat          = &rats[&k];
223      let rat_key      = format!("RAT{:2}", rat);
224      let mut cmd      = TofCommand::new();
225      cmd.command_code = TofCommandCode::SetPreampBias;
226      let mut payload  = PreampBiasConfig::new();
227      payload.rb_id    = rat.rb2_id as u8;
228      if *k as usize >= self.rat_preamp_biases.len() {
229        error!("RAT ID {k} larger than 20!");
230        continue;
231      }
232      payload.biases = self.rat_preamp_biases[&rat_key];
233      cmd.payload = payload.to_bytestream();
234      let tp = cmd.pack();
235      packets.push(tp);
236    }
237    packets
238  }
239}
240
241impl fmt::Display for PreampSettings {
242  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243    let disp : String;
244    match toml::to_string(self) {
245      Err(err) => {
246        error!("Deserialization error! {err}");
247        disp = String::from("-- DESERIALIZATION ERROR! --");
248      }
249      Ok(_disp) => {
250        disp = _disp;
251      }
252    }
253    write!(f, "<PreampBiasSettings :\n{}>", disp)
254  }
255}
256
257impl Default for PreampSettings {
258  fn default() -> Self {
259    Self::new()
260  }
261}
262
263#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
264pub struct LTBThresholdSettings {
265  /// actually apply the below settings
266  pub set_ltb_thresholds    : bool,
267  /// liftof-cc will send commands to set the 
268  /// ltb thresholds
269  pub set_strategy          : ParameterSetStrategy,
270  /// ltb threshold voltages (one set of 3 values per RAT)
271  pub rat_ltb_thresholds    : HashMap<String, [f32;3]>
272}
273
274impl LTBThresholdSettings {
275  pub fn new() -> Self {
276    let default_thresholds = HashMap::from([
277      (String::from("RAT01"), [40.0,32.0,375.0]),
278      (String::from("RAT02"), [40.0,32.0,375.0]),
279      (String::from("RAT03"), [40.0,32.0,375.0]),
280      (String::from("RAT04"), [40.0,32.0,375.0]),
281      (String::from("RAT05"), [40.0,32.0,375.0]),
282      (String::from("RAT06"), [40.0,32.0,375.0]),
283      (String::from("RAT07"), [40.0,32.0,375.0]),
284      (String::from("RAT08"), [40.0,32.0,375.0]),
285      (String::from("RAT09"), [40.0,32.0,375.0]),
286      (String::from("RAT10"), [40.0,32.0,375.0]),
287      (String::from("RAT11"), [40.0,32.0,375.0]),
288      (String::from("RAT12"), [40.0,32.0,375.0]),
289      (String::from("RAT13"), [40.0,32.0,375.0]),
290      (String::from("RAT14"), [40.0,32.0,375.0]),
291      (String::from("RAT15"), [40.0,32.0,375.0]),
292      (String::from("RAT16"), [40.0,32.0,375.0]),
293      (String::from("RAT17"), [40.0,32.0,375.0]),
294      (String::from("RAT18"), [40.0,32.0,375.0]),
295      (String::from("RAT19"), [40.0,32.0,375.0]),
296      (String::from("RAT20"), [40.0,32.0,375.0])]);
297
298      Self {
299        set_ltb_thresholds    : false,
300        set_strategy          : ParameterSetStrategy::ControlServer,
301        rat_ltb_thresholds    : default_thresholds,
302      }
303  }
304
305  #[cfg(feature="database")]
306  pub fn emit_ltb_settings_packets(&self, rats : &HashMap<u8,RAT>) -> Vec<TofPacket> {
307    let mut packets = Vec::<TofPacket>::new();
308    for k in rats.keys() {
309      let rat          = &rats[&k];
310      let rat_key      = format!("RAT{:2}", rat);
311      let mut cmd      = TofCommand::new();
312      cmd.command_code = TofCommandCode::SetLTBThresholds;
313      let mut payload  = LTBThresholdConfig::new();
314      payload.rb_id    = rat.rb1_id as u8;
315      if *k as usize >= self.rat_ltb_thresholds.len() {
316        error!("RAT ID {k} larger than 20!");
317        continue;
318      }
319      payload.thresholds = self.rat_ltb_thresholds[&rat_key];
320      cmd.payload = payload.to_bytestream();
321      let tp = cmd.pack();
322      packets.push(tp);
323    }
324    packets
325  }
326}
327
328impl fmt::Display for LTBThresholdSettings {
329  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330    let disp : String;
331    match toml::to_string(self) {
332      Err(err) => {
333        error!("Deserialization error! {err}");
334        disp = String::from("-- DESERIALIZATION ERROR! --");
335      }
336      Ok(_disp) => {
337        disp = _disp;
338      }
339    }
340    write!(f, "<LTBThresholdSettings :\n{}>", disp)
341  }
342}
343
344impl Default for LTBThresholdSettings {
345  fn default() -> Self {
346    Self::new()
347  }
348}
349
350#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
351pub struct CommandDispatcherSettings {
352  /// Log all commands into this file
353  /// Set to "/dev/null" to turn off.
354  /// The mode will be always "append", since we don't 
355  /// expect a lot of logging
356  pub cmd_log_path               : String,
357  /// The address of the liftof-command & control server
358  /// that is the ip address on the RBNetwork which the 
359  /// liftof-cc instance runs on 
360  /// This address will be used as "PUB" for the CommandDispather
361  /// This address has to be within the RB network
362  pub cc_server_address          : String,   
363  /// The address ("tcp://xx.xx.xx.xx:xxxxx") the tof computer should subscribe to 
364  /// to get commands from the flight computer. This address is within the 
365  /// flight network
366  pub fc_sub_address             : String,
367  /// Interval of time that will elapse from a cmd check to the other
368  pub cmd_listener_interval_sec  : u64,
369  /// Safety mechanism - is this is on, the command listener will deny 
370  /// every request. E.g. in staging mode to guarantee no tinkering
371  pub deny_all_requests          : bool
372}
373
374impl CommandDispatcherSettings {
375  pub fn new() -> Self {
376    Self {
377      cmd_log_path                   : String::from("/home/gaps/log"),
378      cc_server_address              : String::from("tcp://10.0.1.10:42000"),   
379      fc_sub_address                 : String::from("tcp://192.168.37.200:41662"),
380      cmd_listener_interval_sec      : 1,
381      deny_all_requests              : false
382    }
383  }
384}
385
386impl fmt::Display for CommandDispatcherSettings {
387  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
388    let disp = toml::to_string(self).unwrap_or(
389      String::from("-- DESERIALIZATION ERROR! --"));
390    write!(f, "<CommandDispatcherSettings :\n{}>", disp)
391  }
392}
393
394impl Default for CommandDispatcherSettings {
395  fn default() -> Self {
396    Self::new()
397  }
398}
399
400/// Readout strategy for RB (onboard) (RAM) memory buffers
401#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
402pub enum RBBufferStrategy {
403  /// Readout and switch the buffers every
404  /// x events
405  NEvents(u16),
406  /// Adapt to the RB rate and readout the buffers
407  /// so that we get switch them every X seconds.
408  /// (Argument is in seconds
409  AdaptToRate(u16),
410}
411
412impl fmt::Display for RBBufferStrategy {
413  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
414    let r = serde_json::to_string(self).unwrap_or(
415      String::from("N.A. - Invalid RBBufferStrategy (error)"));
416    write!(f, "<RBBufferStrategy: {}>", r)
417  }
418}
419
420
421/// Settings for the specific clients on the RBs (liftof-rb)
422#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
423pub struct RBSettings {
424  /// Don't send events if they have issues. Requires
425  /// EventStatus::Perfect. This can not work in the
426  /// OperationMode RBHighThroughput
427  pub only_perfect_events           : bool,
428  /// Calculate the crc32 sum for each channel and set
429  /// the EventStatus flag accordingly.
430  pub calc_crc32                    : bool,
431  /// tof operation mode - either "StreamAny",
432  /// "RequestReply" or "RBHighThroughput"
433  pub tof_op_mode                   : TofOperationMode,
434  /// if different from 0, activate RB self trigger
435  /// in poisson mode
436  pub trigger_poisson_rate          : u32,
437  /// if different from 0, activate RB self trigger 
438  /// with fixed rate setting
439  pub trigger_fixed_rate            : u32,
440  ///// if different from 0, activate RB self trigger
441  ///// in poisson mode
442  //pub trigger_poisson_rate    : u32,
443  ///// if different from 0, activate RB self trigger 
444  ///// with fixed rate setting
445  //pub trigger_fixed_rate      : u32,
446  /// Either "Physics" or a calibration related 
447  /// data type, e.g. "VoltageCalibration".
448  /// <div class="warning">This might get deprecated in a future version!</div>
449  pub data_type                     : DataType,
450  /// This allows for different strategies on how to readout 
451  /// the RB buffers. The following refers to the NEvent strategy.
452  /// The value when the readout of the RB buffers is triggered.
453  /// This number is in size of full events, which correspond to 
454  /// 18530 bytes. Maximum buffer size is a bit more than 3000 
455  /// events. Smaller buffer allows for a more snappy reaction, 
456  /// but might require more CPU resources (on the board)
457  /// For RBBufferStrategy::AdaptToRate(k), readout (and switch) the buffers every
458  /// k seconds. The size of the buffer will be determined
459  /// automatically depending on the rate.
460  pub rb_buff_strategy               : RBBufferStrategy,
461  /// The general moni interval. Whenever this time in seconds has
462  /// passed, the RB will send a RBMoniData packet
463  pub rb_moni_interval               : f32,
464  /// Powerboard monitoring. Do it every multiple of rb_moni_interval
465  pub pb_moni_every_x                : f32,
466  /// Preamp monitoring. Do it every multiple of rb_moni_interval
467  pub pa_moni_every_x                : f32,
468  /// LTB monitoring. Do it every multiple of rb_moni_interval
469  pub ltb_moni_every_x               : f32,
470  /// Choose between drs deadtime or fpga 
471  pub drs_deadtime_instead_fpga_temp : bool
472}
473
474impl RBSettings {
475  pub fn new() -> Self {
476    Self {
477      only_perfect_events            : false,
478      calc_crc32                     : false,
479      tof_op_mode                    : TofOperationMode::Default,
480      trigger_fixed_rate             : 0,
481      trigger_poisson_rate           : 0,
482      data_type                      : DataType::Physics,
483      rb_buff_strategy               : RBBufferStrategy::AdaptToRate(5),
484      rb_moni_interval               : 0.0,
485      pb_moni_every_x                : 0.0,
486      pa_moni_every_x                : 0.0,
487      ltb_moni_every_x               : 0.0,
488      drs_deadtime_instead_fpga_temp : false
489    }
490  }
491
492  pub fn from_tofrbconfig(&mut self, cfg : &TofRBConfig) {
493    if cfg.rb_moni_interval.is_some() {
494      self.rb_moni_interval = cfg.rb_moni_interval.unwrap() as f32;              
495    }
496    if cfg.rb_moni_interval.is_some() {
497      self.pb_moni_every_x  = cfg.pb_moni_every_x.unwrap() as f32;              
498    }
499    if cfg.rb_moni_interval.is_some() {
500      self.pa_moni_every_x  = cfg.pa_moni_every_x.unwrap() as f32;              
501    }
502    if cfg.rb_moni_interval.is_some() {
503      self.ltb_moni_every_x = cfg.ltb_moni_every_x.unwrap() as f32;              
504    }
505    if cfg.rb_moni_interval.is_some() {
506      self.drs_deadtime_instead_fpga_temp = cfg.drs_deadtime_instead_fpga_temp.unwrap(); 
507    }
508  }
509
510  pub fn get_runconfig(&self) -> RunConfig {
511    // missing here - run id, nevents, nseconds,
512    //
513    let mut rcfg              = RunConfig::new();
514    rcfg.is_active            = true;
515    rcfg.tof_op_mode          = self.tof_op_mode.clone();
516    rcfg.trigger_fixed_rate   = self.trigger_fixed_rate;
517    rcfg.trigger_poisson_rate = self.trigger_poisson_rate;
518    rcfg.data_type            = self.data_type.clone();
519    let buffer_trip : u16;
520    match self.rb_buff_strategy {
521      RBBufferStrategy::NEvents(buff_size) => {
522        buffer_trip = buff_size;
523      },
524      RBBufferStrategy::AdaptToRate(_) => {
525        // For now, let's just set the initial value to
526        // 50 FIXME
527        buffer_trip = 50;
528        //match rate = get_trigger_rate() {
529        //  Err(err) {
530        //    error!("Unable to obtain trigger rate!");
531        //    buffer_trip = 50;
532        //  },
533        //  Ok(rate) => {
534        //    buffer_trip = rate*n_secs as u16;
535        //  }
536        //}
537      }
538    }
539    rcfg.rb_buff_size         = buffer_trip as u16;
540    rcfg
541  }
542}
543
544impl fmt::Display for RBSettings {
545  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
546    let disp = toml::to_string(self).unwrap_or(
547      String::from("-- DESERIALIZATION ERROR! --"));
548    write!(f, "<RBSettings :\n{}>", disp)
549  }
550}
551
552impl Default for RBSettings {
553  fn default() -> Self {
554    Self::new()
555  }
556}
557
558
559/// Settings to change the configuration of the analysis engine 
560/// (pulse extraction)
561#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
562pub struct AnalysisEngineSettings {
563  /// pulse integration start
564  pub integration_start      : f32,
565  /// pulse integration window
566  pub integration_window     : f32, 
567  /// Pedestal threshold
568  pub pedestal_thresh        : f32,
569  /// Pedestal begin bin
570  pub pedestal_begin_bin     : usize,
571  /// Pedestal width (bins)
572  pub pedestal_win_bins      : usize,
573  /// Use a zscore algorithm to find the peaks instead
574  /// of Jeff's algorithm
575  pub use_zscore             : bool,
576  /// Peakfinding start time
577  pub find_pks_t_start       : f32,
578  /// Peakfinding window
579  pub find_pks_t_window      : f32,
580  /// Minimum peaksize (bins)
581  pub min_peak_size          : usize,
582  /// Threshold for peak recognition
583  pub find_pks_thresh        : f32,
584  /// Max allowed peaks
585  pub max_peaks              : usize,
586  /// Timing CFG fraction
587  pub cfd_fraction           : f32,
588  /// Time over threshold threshold in mV
589  pub tot_threshold          : Option<f32>
590}
591
592impl AnalysisEngineSettings {
593  pub fn new() -> Self {
594    Self {
595      integration_start         : 270.0,
596      integration_window        : 70.0, 
597      pedestal_thresh           : 10.0,
598      pedestal_begin_bin        : 10,
599      pedestal_win_bins         : 50,
600      use_zscore                : false,
601      find_pks_t_start          : 270.0,
602      find_pks_t_window         : 70.0,
603      min_peak_size             : 3,
604      find_pks_thresh           : 10.0,
605      max_peaks                 : 5,
606      cfd_fraction              : 0.2,
607      tot_threshold             : Some(500.0),
608    }
609  }
610}
611
612impl fmt::Display for AnalysisEngineSettings {
613  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
614    let disp = toml::to_string(self).unwrap_or(
615      String::from("-- DESERIALIZATION ERROR! --"));
616    write!(f, "<AnalysisEngineSettings :\n{}>", disp)
617  }
618}
619
620impl Default for AnalysisEngineSettings {
621  fn default() -> Self {
622    Self::new()
623  }
624}
625
626/// Settings to change the configuration of the TOF Eventbuilder
627#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
628pub struct TofEventBuilderSettings {
629  pub cachesize           : u32,
630  pub n_mte_per_loop      : u32,
631  pub n_rbe_per_loop      : u32,
632  /// The timeout parameter for the TofEvent. If not
633  /// complete after this time, send it onwards anyway
634  pub te_timeout_sec      : u32,
635  /// try to sort the events by id (uses more resources)
636  pub sort_events         : bool,
637  pub build_strategy      : BuildStrategy,
638  pub greediness          : u8,
639  pub wait_nrb            : u8,
640  pub hb_send_interval    : u16,
641  /// Allows to restrict saving the event to disk
642  /// based on the interesting event parameters
643  /// (These are minimum values)
644  pub only_save_interesting : bool,
645  pub thr_n_hits_umb        : u8,
646  pub thr_n_hits_cbe        : u8,
647  pub thr_n_hits_cor        : u8,
648  pub thr_tot_edep_umb      : f32,
649  pub thr_tot_edep_cbe      : f32,
650  pub thr_tot_edep_cor      : f32,
651}
652
653impl TofEventBuilderSettings {
654  pub fn new() -> TofEventBuilderSettings {
655    TofEventBuilderSettings {
656      cachesize             : 100000,
657      n_mte_per_loop        : 1,
658      n_rbe_per_loop        : 40,
659      te_timeout_sec        : 30,
660      sort_events           : false,
661      build_strategy        : BuildStrategy::Adaptive,
662      greediness            : 3,
663      wait_nrb              : 40,
664      hb_send_interval      : 30,
665      only_save_interesting : false,
666      thr_n_hits_umb        : 0,
667      thr_n_hits_cbe        : 0,
668      thr_n_hits_cor        : 0,
669      thr_tot_edep_umb      : 0.0,
670      thr_tot_edep_cbe      : 0.0,
671      thr_tot_edep_cor      : 0.0,
672    }
673  }
674
675  pub fn from_tofeventbuilderconfig(&mut self, cfg : &TOFEventBuilderConfig) {
676    if cfg.cachesize.is_some() {
677      self.cachesize = cfg.cachesize.unwrap();
678    }
679    if cfg.n_mte_per_loop.is_some() {
680      self.n_mte_per_loop = cfg.n_mte_per_loop.unwrap();
681    }
682    if cfg.n_rbe_per_loop.is_some() {
683      self.n_rbe_per_loop = cfg.n_rbe_per_loop.unwrap();
684    }
685    if cfg.te_timeout_sec.is_some() {
686      self.te_timeout_sec = cfg.te_timeout_sec.unwrap();
687    }
688    if cfg.sort_events.is_some() {
689      self.sort_events = cfg.sort_events.unwrap();
690    }
691    if cfg.build_strategy.is_some() {
692      self.build_strategy = cfg.build_strategy.unwrap();
693    }
694    if cfg.greediness.is_some() {
695      self.greediness = cfg.greediness.unwrap();
696    }
697    if cfg.wait_nrb.is_some() {
698      self.wait_nrb = cfg.wait_nrb.unwrap();
699    }
700    if cfg.hb_send_interval.is_some() {
701      self.hb_send_interval = cfg.hb_send_interval.unwrap();
702    }
703    if cfg.only_save_interesting.is_some() {
704      self.only_save_interesting = cfg.only_save_interesting.unwrap();
705    }
706    if cfg.thr_n_hits_umb.is_some() { 
707      self.thr_n_hits_umb = cfg.thr_n_hits_umb.unwrap();
708    }
709    if cfg.thr_n_hits_cbe.is_some() {      
710      self.thr_n_hits_cbe = cfg.thr_n_hits_cbe.unwrap();
711    }
712    if cfg.thr_n_hits_cor.is_some()   {
713      self.thr_n_hits_cor = cfg.thr_n_hits_cor.unwrap();
714    }
715    if cfg.thr_tot_edep_umb.is_some() {    
716      self.thr_tot_edep_umb = cfg.thr_tot_edep_umb.unwrap();
717    }
718    if cfg.thr_tot_edep_cbe.is_some() {    
719      self.thr_tot_edep_cbe = cfg.thr_tot_edep_cbe.unwrap();
720    }
721    if cfg.thr_tot_edep_cor.is_some() {    
722      self.thr_tot_edep_cor = cfg.thr_tot_edep_cor.unwrap();
723    }
724  }
725}
726
727impl fmt::Display for TofEventBuilderSettings {
728  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
729    let disp = toml::to_string(self).unwrap_or(
730      String::from("-- DESERIALIZATION ERROR! --"));
731    write!(f, "<TofEventBuilderSettings :\n{}>", disp)
732  }
733}
734
735impl Default for TofEventBuilderSettings {
736  fn default() -> Self {
737    Self::new()
738  }
739}
740
741/// Configure data storage and packet publishing
742#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
743pub struct DataPublisherSettings {
744  /// location to store data on TOF computer
745  pub data_dir                  : String,
746  /// The data written on disk gets divided into 
747  /// files of a fixed size. 
748  pub mbytes_per_file           : usize,
749  /// The address the flight computer should subscribe 
750  /// to to get tof packets
751  pub fc_pub_address            : String,
752  /// Mark a certain fraction of events as to be discarded, 
753  /// that is not to be stored on disk
754  /// 1 = Throw away all events, 0 = throw away no events
755  pub discard_event_fraction    : f32,
756  ///// Don't save events which are non-interesting
757  //pub discard_non_interesting   : bool,
758  //pub filter_interesting_numb   : u8,
759  //pub filter_interesting_ncbe   : u8,
760  //pub filter_interesting_n
761  /// Send also MastertriggerPackets (this should be 
762  /// turned off in flight - only useful if 
763  /// send_flight_packets is true, otherwise
764  /// MTB events will get sent as part of TofEvents
765  pub send_mtb_event_packets    : bool,
766  /// switch off waveform sending (in case of we 
767  /// are sending flight packets)
768  pub send_rbwaveform_packets   : bool,
769  /// send only a fraction of all RBWaveform packets
770  /// 1 = all events, 1000 = every 1/1000 event
771  pub send_rbwf_every_x_event   : u32,
772  pub send_tof_summary_packets  : bool,
773  pub send_tof_event_packets    : bool,
774  /// Send the RBCalibration to ground
775  pub send_cali_packets         : bool,
776  pub hb_send_interval          : u16,
777}
778
779impl DataPublisherSettings {
780  pub fn new() -> Self {
781    Self {
782      data_dir                  : String::from(""),
783      mbytes_per_file           : 420,
784      fc_pub_address            : String::from(""),
785      discard_event_fraction    : 0.0,
786      send_mtb_event_packets    : false,
787      send_rbwaveform_packets   : false,
788      send_rbwf_every_x_event   : 1,
789      send_tof_summary_packets  : true,
790      send_tof_event_packets    : false,
791      send_cali_packets         : true,
792      hb_send_interval          : 30,
793    }
794  }
795  
796  pub fn from_datapublisherconfig(&mut self, cfg : &DataPublisherConfig) {
797    if cfg.mbytes_per_file.is_some() {
798      self.mbytes_per_file = cfg.mbytes_per_file.unwrap() as usize;
799    }
800    if cfg.discard_event_fraction.is_some() {
801      self.discard_event_fraction = cfg.discard_event_fraction.unwrap();
802    }
803    if cfg.send_mtb_event_packets.is_some() {
804      self.send_mtb_event_packets = cfg.send_mtb_event_packets.unwrap();
805    }
806    if cfg.send_rbwaveform_packets.is_some() {
807      self.send_rbwaveform_packets = cfg.send_rbwaveform_packets.unwrap();
808    }
809    if cfg.send_rbwf_every_x_event.is_some() {
810      self.send_rbwf_every_x_event = cfg.send_rbwf_every_x_event.unwrap();
811    }
812    if cfg.send_tof_summary_packets.is_some() {
813      self.send_tof_summary_packets = cfg.send_tof_summary_packets.unwrap();
814    }
815    if cfg.send_tof_event_packets.is_some() {
816      self.send_tof_event_packets = cfg.send_tof_event_packets.unwrap();
817    }
818    if cfg.hb_send_interval.is_some() {
819      self.hb_send_interval = cfg.hb_send_interval.unwrap();
820    }
821  }
822}
823
824impl fmt::Display for DataPublisherSettings {
825  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
826    let disp = toml::to_string(self).unwrap_or(
827      String::from("-- DESERIALIZATION ERROR! --"));
828    write!(f, "<DataPublisherSettings :\n{}>", disp)
829  }
830}
831
832impl Default for DataPublisherSettings {
833  fn default() -> Self {
834    Self::new()
835  }
836}
837
838
839#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
840#[cfg_attr(feature="pybindings", pyclass)]
841pub struct LiftofSettings {
842  /// read run .toml files from this directory and 
843  /// automotically work through them 1by1
844  pub staging_dir                : String,
845  /// default location for RBCalibration files
846  pub calibration_dir            : String,
847  /// default location for the database
848  pub db_path                    : String,
849  /// Runtime in seconds
850  pub runtime_sec                : u64,
851  /// The UDP port to be used to get packets from the 
852  /// MTB
853  pub mtb_address                : String,
854  /// The interval (in seconds) to retrive CPUMoniData from 
855  /// the TOF CPU
856  pub cpu_moni_interval_sec      : u64,
857  /// In an intervall from 1-50, these RB simply do not exist
858  /// or might have never existed. Always ingore these
859  pub rb_ignorelist_always       : Vec<u8>,
860  /// ignore these specific RB for this run
861  pub rb_ignorelist_run          : Vec<u8>,
862  /// Should TofHits be generated?
863  pub run_analysis_engine        : bool,
864  /// RB contols both, LTB and PB
865  pub rb_controls_pb_and_ltb     : Option<Vec<u8>>,
866  /// Run a full RB calibration before run 
867  /// start?
868  pub pre_run_calibration        : bool,
869  /// Should the waveforms which go into te calibration 
870  /// be saved in the package?
871  pub save_cali_wf               : bool,
872  /// Do a verification run before each run? The purpose 
873  /// of the verification run is to generate a "DetectorStatus"
874  /// packet. If a verification run is desired, change this 
875  /// number to the number of seconds to do the verification 
876  /// run
877  #[deprecated(since = "0.11", note = "Use flag verfication_rnn and runtime instead!")]
878  pub verification_runtime_sec   : Option<u32>,
879  /// If this is set, don't save anything to disk 
880  /// and just transmit the TofDetectorStatus packet
881  pub verification_run           : Option<bool>,
882  /// Settings to control the MTB
883  pub mtb_settings               : MTBSettings,
884  /// Settings for the TOF event builder
885  pub event_builder_settings     : TofEventBuilderSettings,
886  /// Settings for the analysis engine
887  pub analysis_engine_settings   : AnalysisEngineSettings,
888  /// Configure data publshing and saving on local disc
889  pub data_publisher_settings    : DataPublisherSettings,
890  /// Configure cmmand reception and sending
891  pub cmd_dispatcher_settings    : CommandDispatcherSettings,
892  /// Settings for the individual RBs
893  pub rb_settings                : RBSettings,
894  /// Mask individual channels (e.g. dead preamps) 
895  /// for the readout boards
896  pub rb_channel_mask            : ChannelMaskSettings,
897  /// Preamp configuration
898  pub preamp_settings            : PreampSettings,
899  /// LTB threshold configuration
900  pub ltb_settings               : LTBThresholdSettings
901}
902
903impl LiftofSettings {
904  pub fn new() -> Self {
905    LiftofSettings {
906      staging_dir               : String::from("/home/gaps/liftof-staging"),
907      calibration_dir           : String::from(""),
908      db_path                   : String::from("/home/gaps/config/gaps_flight.db"),
909      runtime_sec               : 0,
910      mtb_address               : String::from("10.0.1.10:50001"),
911      cpu_moni_interval_sec     : 60,
912      rb_ignorelist_always      : Vec::<u8>::new(),
913      rb_ignorelist_run         : Vec::<u8>::new(),
914      rb_controls_pb_and_ltb    : None,
915      run_analysis_engine       : true,
916      pre_run_calibration       : false,
917      save_cali_wf              : false,
918      verification_runtime_sec  : None, // no verification run per default
919      verification_run          : None,
920      mtb_settings              : MTBSettings::new(),
921      event_builder_settings    : TofEventBuilderSettings::new(),
922      analysis_engine_settings  : AnalysisEngineSettings::new(),
923      data_publisher_settings   : DataPublisherSettings::new(),
924      cmd_dispatcher_settings   : CommandDispatcherSettings::new(),
925      rb_settings               : RBSettings::new(),
926      rb_channel_mask           : ChannelMaskSettings::new(),
927      preamp_settings           : PreampSettings::new(),
928      ltb_settings              : LTBThresholdSettings::new(),
929    }
930  }  
931
932  /// Change the settings according to the ones in the 
933  /// given config 
934  pub fn from_tofrunconfig(&mut self, cfg : &TofRunConfig) {
935    if cfg.runtime.is_some() {
936      self.runtime_sec = cfg.runtime.unwrap() as u64;
937    }
938  }
939
940  /// Write the settings to a toml file
941  pub fn to_toml(&self, mut filename : String) {
942    if !filename.ends_with(".toml") {
943      filename += ".toml";
944    }
945    info!("Will write to file {}!", filename);
946    match File::create(&filename) {
947      Err(err) => {
948        error!("Unable to open file {}! {}", filename, err);
949      }
950      Ok(mut file) => {
951        match toml::to_string_pretty(&self) {
952          Err(err) => {
953            error!("Unable to serialize toml! {err}");
954          }
955          Ok(toml_string) => {
956            match file.write_all(toml_string.as_bytes()) {
957              Err(err) => error!("Unable to write to file {}! {}", filename, err),
958              Ok(_)    => debug!("Wrote settings to {}!", filename)
959            }
960          }
961        }
962      }
963    }
964  }
965
966  /// Write the settings to a json file
967  pub fn to_json(&self, mut filename : String) {
968    if !filename.ends_with(".json") {
969      filename += ".json";
970    }
971    info!("Will write to file {}!", filename);
972    match File::create(&filename) {
973      Err(err) => {
974        error!("Unable to open file {}! {}", filename, err);
975      }
976      Ok(file) => {
977        match serde_json::to_writer_pretty(file, &self) {
978          Err(err) => {
979            error!("Unable to serialize json! {err}");
980          }
981          Ok(_) => debug!("Wrote settings to {}!", filename)
982        }
983      }
984    }
985  }
986
987  pub fn from_toml(filename : &str) -> Result<LiftofSettings, SerializationError> {
988    match File::open(filename) {
989      Err(err) => {
990        error!("Unable to open {}! {}", filename, err);
991        return Err(SerializationError::TomlDecodingError);
992      }
993      Ok(mut file) => {
994        let mut toml_string = String::from("");
995        match file.read_to_string(&mut toml_string) {
996          Err(err) => {
997            error!("Unable to read {}! {}", filename, err);
998            return Err(SerializationError::TomlDecodingError);
999          }
1000          Ok(_) => {
1001            match toml::from_str(&toml_string) {
1002              Err(err) => {
1003                error!("Can't interpret toml! {}", err);
1004                return Err(SerializationError::TomlDecodingError);
1005              }
1006              Ok(settings) => {
1007                return Ok(settings);
1008              }
1009            }
1010          }
1011        }
1012      }
1013    }
1014  }
1015}
1016
1017impl fmt::Display for LiftofSettings {
1018  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1019    let disp : String;
1020    match toml::to_string(self) {
1021      Err(err) => {
1022        println!("Deserialization error! {err}");
1023        disp = String::from("-- DESERIALIZATION ERROR! --");
1024      }
1025      Ok(_disp) => {
1026        disp = _disp;
1027      }
1028    }
1029    write!(f, "<LiftofSettings :\n{}>", disp)
1030  }
1031}
1032
1033impl Default for LiftofSettings {
1034  fn default() -> Self {
1035    Self::new()
1036  }
1037}
1038
1039#[cfg(feature="pybindings")]
1040#[pymethods]
1041impl LiftofSettings {
1042
1043  /// Read settings from a .toml file
1044  ///
1045  /// # Arugments:
1046  ///
1047  /// * filename : A .toml file with settings fro the 
1048  ///              liftof flight suite
1049  #[staticmethod]
1050  fn from_file(filename : &str) -> PyResult<Self> {
1051    match LiftofSettings::from_toml(filename) {
1052      Ok(settings) => {
1053        return Ok(settings);
1054      }
1055      Err(err) => {
1056        return Err(PyValueError::new_err(err.to_string()));
1057      }
1058    }
1059  }
1060}
1061
1062#[cfg(feature="pybindings")]
1063pythonize!(LiftofSettings);
1064
1065//----------------------------------------------------
1066
1067/// Readoutboard configuration for a specific run
1068#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1069pub struct LiftofRBConfig {
1070  /// limit run time to number of seconds
1071  pub nseconds                : u32,
1072  /// tof operation mode - either "StreamAny",
1073  /// "RequestReply" or "RBHighThroughput"
1074  pub tof_op_mode             : TofOperationMode,
1075  /// if different from 0, activate RB self trigger
1076  /// in poisson mode
1077  pub trigger_poisson_rate    : u32,
1078  /// if different from 0, activate RB self trigger 
1079  /// with fixed rate setting
1080  pub trigger_fixed_rate      : u32,
1081  /// Either "Physics" or a calibration related 
1082  /// data type, e.g. "VoltageCalibration".
1083  /// <div class="warning">This might get deprecated in a future version!</div>
1084  pub data_type               : DataType,
1085  /// The value when the readout of the RB buffers is triggered.
1086  /// This number is in size of full events, which correspond to 
1087  /// 18530 bytes. Maximum buffer size is a bit more than 3000 
1088  /// events. Smaller buffer allows for a more snappy reaction, 
1089  /// but might require more CPU resources (on the board)
1090  pub rb_buff_size            : u16
1091}
1092
1093impl LiftofRBConfig {
1094
1095  pub fn new() -> Self {
1096    Self {
1097      nseconds                : 0,
1098      tof_op_mode             : TofOperationMode::Default,
1099      trigger_poisson_rate    : 0,
1100      trigger_fixed_rate      : 0,
1101      data_type               : DataType::Unknown, 
1102      rb_buff_size            : 0,
1103    }
1104  }
1105}
1106
1107impl Serialization for LiftofRBConfig {
1108  const HEAD               : u16   = 43690; //0xAAAA
1109  const TAIL               : u16   = 21845; //0x5555
1110  const SIZE               : usize = 24; // bytes including HEADER + FOOTER
1111  
1112  fn from_bytestream(bytestream : &Vec<u8>,
1113                     pos        : &mut usize)
1114    -> Result<Self, SerializationError> {
1115    let mut pars = Self::new();
1116    Self::verify_fixed(bytestream, pos)?;
1117    pars.nseconds                = parse_u32 (bytestream, pos);
1118    pars.tof_op_mode           
1119      = TofOperationMode::try_from(
1120          parse_u8(bytestream, pos))
1121      .unwrap_or_else(|_| TofOperationMode::Unknown);
1122    pars.trigger_poisson_rate    = parse_u32 (bytestream, pos);
1123    pars.trigger_fixed_rate      = parse_u32 (bytestream, pos);
1124    pars.data_type    
1125      = DataType::try_from(parse_u8(bytestream, pos))
1126      .unwrap_or_else(|_| DataType::Unknown);
1127    pars.rb_buff_size = parse_u16(bytestream, pos);
1128    *pos += 2; // for the tail 
1129    //_ = parse_u16(bytestream, pos);
1130    Ok(pars)
1131  }
1132  
1133  fn to_bytestream(&self) -> Vec<u8> {
1134    let mut stream = Vec::<u8>::with_capacity(Self::SIZE);
1135    stream.extend_from_slice(&Self::HEAD.to_le_bytes());
1136    stream.extend_from_slice(&self.  nseconds.to_le_bytes());
1137    stream.extend_from_slice(&(self.tof_op_mode as u8).to_le_bytes());
1138    stream.extend_from_slice(&self.trigger_poisson_rate.to_le_bytes());
1139    stream.extend_from_slice(&self.trigger_fixed_rate.to_le_bytes());
1140    stream.extend_from_slice(&(self.data_type as u8).to_le_bytes());
1141    stream.extend_from_slice(&self.rb_buff_size.to_le_bytes());
1142    stream.extend_from_slice(&Self::TAIL.to_le_bytes());
1143    stream
1144  }
1145}
1146
1147impl Default for LiftofRBConfig {
1148  fn default() -> Self {
1149    Self::new()
1150  }
1151}
1152
1153impl fmt::Display for LiftofRBConfig {
1154  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1155    write!(f, 
1156"<LiftofRBConfig -- is_active : true
1157    nseconds     : {}
1158    TOF op. mode : {}
1159    data type    : {}
1160    tr_poi_rate  : {}
1161    tr_fix_rate  : {}
1162    buff size    : {} [events]>",
1163      self.nseconds,
1164      self.tof_op_mode,
1165      self.data_type,
1166      self.trigger_poisson_rate,
1167      self.trigger_fixed_rate,
1168      self.rb_buff_size)
1169  }
1170}
1171
1172//#[cfg(feature = "random")]
1173//impl FromRandom for LiftofRBConfig {
1174//    
1175//  fn from_random() -> Self {
1176//    let mut cfg = Self::new();
1177//    let mut rng  = rand::thread_rng();
1178//    cfg.runid                   = rng.gen::<u32>();
1179//    cfg.is_active               = rng.gen::<bool>();
1180//    cfg.nevents                 = rng.gen::<u32>();
1181//    cfg.nseconds                = rng.gen::<u32>();
1182//    cfg.tof_op_mode             = TofOperationMode::from_random();
1183//    cfg.trigger_poisson_rate    = rng.gen::<u32>();
1184//    cfg.trigger_fixed_rate      = rng.gen::<u32>();
1185//    cfg.data_type               = DataType::from_random();
1186//    cfg.rb_buff_size            = rng.gen::<u16>();
1187//    cfg
1188//  }
1189//}
1190//
1191//#[cfg(feature = "random")]
1192//#[test]
1193//fn serialization_runconfig() {
1194//  for k in 0..100 {
1195//    let cfg  = LiftofRBConfig::from_random();
1196//    let test = LiftofRBConfig::from_bytestream(&cfg.to_bytestream(), &mut 0).unwrap();
1197//    assert_eq!(cfg, test);
1198//
1199//    let cfg_json = serde_json::to_string(&cfg).unwrap();
1200//    let test_json 
1201//      = serde_json::from_str::<LiftofRBConfig>(&cfg_json).unwrap();
1202//    assert_eq!(cfg, test_json);
1203//  }
1204//}
1205// #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
1206// pub struct ChannelMaskSettings {
1207//   /// actually apply the below settings
1208//   pub set_channel_mask   : bool,
1209//   /// liftof-cc will send commands to set the 
1210//   /// preamp bias voltages
1211//   pub set_strategy           : ParameterSetStrategy,
1212//   /// channels to mask (one set of 18 values per RAT)
1213//   pub rat_channel_mask     : HashMap<String, [bool;18]>
1214// }
1215
1216/// Ignore RB channnels
1217///
1218/// The values in these arrays correspond to 
1219/// (physical) channels 1-9
1220#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
1221pub struct ChannelMaskSettings {
1222  /// actually apply the below settings
1223  pub set_channel_mask   : bool,
1224  /// The set strat defines who should acutally set
1225  /// the parameters. Will that be done by each board
1226  /// independently (ParameterSetStrategy::Board) or
1227  /// will a command be sent by liftof-cc 
1228  /// (ParameterSetStrategy::ControlServer)
1229  pub set_strategy           : ParameterSetStrategy,
1230  /// channels to mask (one set of 9 values per RB)
1231  /// "true" means the channel is enabled, "false", 
1232  /// disabled
1233  pub rb_channel_mask     : HashMap<String, [bool;9]>
1234}
1235
1236impl ChannelMaskSettings {
1237  pub fn new() -> Self {
1238    let mut default_thresholds = HashMap::<String, [bool; 9]>::new();
1239    for k in 1..51 {
1240      let key = format!("RB{k:02}");
1241      default_thresholds.insert(key, [true;9]);
1242    }
1243//    let default_thresholds = HashMap::from([
1244//      (String::from("RAT01"), [false; 9]),
1245//      (String::from("RAT02"), [false; 9]),
1246//      (String::from("RAT03"), [false; 9]),
1247//      (String::from("RAT04"), [false; 9]),
1248//      (String::from("RAT05"), [false; 9]),
1249//      (String::from("RAT06"), [false; 9]),
1250//      (String::from("RAT07"), [false; 9]),
1251//      (String::from("RAT08"), [false; 9]),
1252//      (String::from("RAT09"), [false; 9]),
1253//      (String::from("RAT10"), [false; 9]),
1254//      (String::from("RAT11"), [false; 9]),
1255//      (String::from("RAT12"), [false; 9]),
1256//      (String::from("RAT13"), [false; 9]),
1257//      (String::from("RAT14"), [false; 9]),
1258//      (String::from("RAT15"), [false; 9]),
1259//      (String::from("RAT16"), [false; 9]),
1260//      (String::from("RAT17"), [false; 9]),
1261//      (String::from("RAT18"), [false; 9]),
1262//      (String::from("RAT19"), [false; 9]),
1263//      (String::from("RAT20"), [false; 9])]);
1264
1265      Self {
1266        set_channel_mask    : false,
1267        set_strategy          : ParameterSetStrategy::ControlServer,
1268        rb_channel_mask    : default_thresholds,
1269      }
1270  }
1271
1272  #[cfg(feature="database")]
1273  pub fn emit_ch_mask_packets(&self, rbs : &HashMap<u8,RAT>) -> Vec<TofPacket> {
1274    let mut packets = Vec::<TofPacket>::new();
1275    for k in rbs.keys() {
1276      let rb          = &rbs[&k];
1277      let rb_key      = format!("RB{:2}", rb);
1278      let mut cmd      = TofCommand::new();
1279      cmd.command_code = TofCommandCode::SetRBChannelMask;
1280      let mut payload  = RBChannelMaskConfig::new();
1281      payload.rb_id    = rb.rb2_id as u8;
1282      if *k as usize >= self.rb_channel_mask.len() {
1283        error!("RB ID {k} larger than 46!");
1284        continue;
1285      }
1286      payload.channels = self.rb_channel_mask[&rb_key];
1287      cmd.payload = payload.to_bytestream();
1288      let tp = cmd.pack();
1289      packets.push(tp);
1290    }
1291    packets
1292  }
1293}
1294impl fmt::Display for ChannelMaskSettings {
1295  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1296    let disp : String;
1297    match toml::to_string(self) {
1298      Err(err) => {
1299        error!("Deserialization error! {err}");
1300        disp = String::from("-- DESERIALIZATION ERROR! --");
1301      }
1302      Ok(_disp) => {
1303        disp = _disp;
1304      }
1305    }
1306    write!(f, "<RBChannelMaskConfig :\n{}>", disp)
1307  }
1308}
1309
1310impl Default for ChannelMaskSettings {
1311  fn default() -> Self {
1312    Self::new()
1313  }
1314}
1315
1316#[cfg(feature="random")]
1317#[test]
1318fn mtb_config() {
1319
1320  for _ in 0..100 {
1321    let cfg  = TriggerConfig::from_random();
1322    let mut settings = MTBSettings::new();
1323    settings.from_triggerconfig(&cfg);
1324    let test = settings.emit_triggerconfig();
1325    if cfg.gaps_trigger_use_beta.is_some() {
1326      assert_eq!(cfg.gaps_trigger_use_beta, test.gaps_trigger_use_beta);
1327    }
1328    if cfg.prescale.is_some() {
1329      assert_eq!(cfg.prescale, test.prescale);
1330    }
1331    if cfg.trigger_type.is_some() {
1332      assert_eq!(cfg.trigger_type, test.trigger_type);
1333    }
1334    if cfg.use_combo_trigger.is_some() {
1335      assert_eq!(cfg.use_combo_trigger, test.use_combo_trigger);
1336    }
1337    if cfg.combo_trigger_type.is_some() {
1338      assert_eq!(cfg.combo_trigger_type, test.combo_trigger_type);
1339    }
1340    if cfg.combo_trigger_prescale.is_some() {
1341      assert_eq!(cfg.combo_trigger_prescale, test.combo_trigger_prescale);
1342    }
1343    if cfg.trace_suppression.is_some() {
1344      assert_eq!(cfg.trace_suppression, test.trace_suppression);
1345    }
1346    if cfg.mtb_moni_interval.is_some() {
1347      assert_eq!(cfg.mtb_moni_interval, test.mtb_moni_interval);
1348    }
1349    if cfg.tiu_ignore_busy.is_some() {
1350      assert_eq!(cfg.tiu_ignore_busy, test.tiu_ignore_busy);
1351    }
1352    if cfg.hb_send_interval.is_some() {
1353      assert_eq!(cfg.hb_send_interval, test.hb_send_interval);
1354    }
1355  }
1356}
1357
1358#[test]
1359fn write_config_file() {
1360  let settings = LiftofSettings::new();
1361  //println!("{}", settings);
1362  settings.to_toml(String::from("liftof-config-test.toml"));
1363}
1364
1365#[test]
1366fn read_config_file() {
1367  let _settings = LiftofSettings::from_toml("liftof-config-test.toml");
1368}
1369
1370