liftof_lib/
lib.rs

1pub mod master_trigger;
2pub mod settings;
3pub mod constants;
4pub mod thread_control;
5pub mod sine_fitter;
6
7use constants::{
8    DEFAULT_LTB_ID,
9};
10
11use std::thread;
12use std::time::Duration;
13use std::os::raw::c_int;
14use std::sync::{
15    Arc,
16    Mutex,
17};
18
19use chrono::Utc;
20
21#[cfg(feature="database")]
22use core::f32::consts::PI;
23
24#[cfg(feature="database")]
25use half::f16;
26
27pub use master_trigger::{
28    master_trigger,
29    MTBSettings,
30};
31
32pub use settings::{
33    LiftofSettings,
34    AnalysisEngineSettings,
35};
36
37use std::fmt;
38
39use std::path::PathBuf;
40use std::fs::read_to_string;
41use std::io::{
42    Write,
43};
44
45use std::collections::HashMap;
46use colored::{
47    Colorize,
48    ColoredString
49};
50
51use serde_json::Value;
52
53use log::Level;
54
55#[macro_use] extern crate log;
56extern crate env_logger;
57
58use signal_hook::iterator::Signals;
59use signal_hook::consts::signal::{
60  SIGTERM,
61  SIGINT
62};
63
64use tof_dataclasses::DsiLtbRBMapping;
65#[cfg(feature="database")]
66use tof_dataclasses::database::ReadoutBoard;
67
68#[cfg(feature="database")]
69use tof_dataclasses::constants::NWORDS;
70#[cfg(feature="database")]
71use tof_dataclasses::errors::AnalysisError;
72use tof_dataclasses::errors::SetError;
73#[cfg(feature="database")]
74use tof_dataclasses::events::{
75  RBEvent,
76  TofHit,
77};
78
79#[cfg(feature="database")]
80use tof_dataclasses::analysis::{
81  calculate_pedestal,
82  integrate,
83  cfd_simple,
84  find_peaks,
85};
86
87use tof_dataclasses::RBChannelPaddleEndIDMap;
88
89use crate::thread_control::ThreadControl;
90
91use clap::{arg,
92  Args,
93};
94
95pub const MT_MAX_PACKSIZE   : usize = 512;
96pub const DATAPORT          : u32   = 42000;
97pub const ASSET_DIR         : &str  = "/home/gaps/assets/"; 
98pub const LIFTOF_LOGO_SHOW  : &str  = "
99                                  ___                         ___           ___     
100                                 /\\__\\                       /\\  \\         /\\__\\    
101                    ___         /:/ _/_         ___         /::\\  \\       /:/ _/_   
102                   /\\__\\       /:/ /\\__\\       /\\__\\       /:/\\:\\  \\     /:/ /\\__\\  
103    ___     ___   /:/__/      /:/ /:/  /      /:/  /      /:/  \\:\\  \\   /:/ /:/  /  
104   /\\  \\   /\\__\\ /::\\  \\     /:/_/:/  /      /:/__/      /:/__/ \\:\\__\\ /:/_/:/  /   
105   \\:\\  \\ /:/  / \\/\\:\\  \\__  \\:\\/:/  /      /::\\  \\      \\:\\  \\ /:/  / \\:\\/:/  /    
106    \\:\\  /:/  /   ~~\\:\\/\\__\\  \\::/__/      /:/\\:\\  \\      \\:\\  /:/  /   \\::/__/     
107     \\:\\/:/  /       \\::/  /   \\:\\  \\      \\/__\\:\\  \\      \\:\\/:/  /     \\:\\  \\     
108      \\::/  /        /:/  /     \\:\\__\\          \\:\\__\\      \\::/  /       \\:\\__\\    
109       \\/__/         \\/__/       \\/__/           \\/__/       \\/__/         \\/__/    
110
111          (LIFTOF - liftof is for tof, Version 0.10 'LELEWAA', Mar 2024)
112          >> with support from the Hawaiian islands \u{1f30a}\u{1f308}\u{1f965}\u{1f334}
113
114          * Documentation
115          ==> GitHub   https://github.com/GAPS-Collab/gaps-online-software/tree/LELEWAA-0.10
116          ==> API docs https://gaps-collab.github.io/gaps-online-software/
117
118  ";
119
120///// Routine to end the liftof-cc program, finish up with current run 
121///// and clean up
122/////
123///// FIXME - maybe this should go to liftof-cc
124//pub fn end_liftof_cc(thread_control     : Arc<Mutex<ThreadControl>>) {
125//  match thread_control.try_lock() {
126//    Ok(mut tc) => {
127//      //println!("== ==> [signal_handler] acquired thread_control lock!");
128//      //println!("Tread control {:?}", tc);
129//      if !tc.thread_cmd_dispatch_active 
130//      && !tc.thread_data_sink_active
131//      && !tc.thread_event_bldr_active 
132//      && !tc.thread_master_trg_active  {
133//        println!(">> So long and thanks for all the \u{1F41F} <<"); 
134//        exit(0);
135//      }
136//      tc.stop_flag = true;
137//      println!("== ==> [signal_handler] Stop flag is set, we are waiting for threads to finish...");
138//      //println!("{}", tc);
139//    }
140//    Err(err) => {
141//      error!("Can't acquire lock for ThreadControl! {err}");
142//    }
143//  }
144//}
145
146/// Handle incoming POSIX signals
147pub fn signal_handler(thread_control     : Arc<Mutex<ThreadControl>>) {
148  let sleep_time = Duration::from_millis(300);
149  let mut signals = Signals::new(&[SIGTERM, SIGINT]).expect("Unknown signals");
150  'main: loop {
151    thread::sleep(sleep_time);
152
153    // check pending signals and handle
154    // SIGTERM and SIGINT
155    for signal in signals.pending() {
156      match signal as c_int {
157        SIGTERM | SIGINT => {
158          println!("=> {}", String::from("SIGTERM or SIGINT received. Maybe Ctrl+C has been pressed! Commencing program shutdown!").red().bold());
159          match thread_control.lock() {
160            Ok(mut tc) => {
161              tc.sigint_recvd = true;
162            }
163            Err(err) => {
164              error!("Can't acquire lock for ThreadControl! {err}");
165            },
166          }
167          break 'main; // now end myself
168        } 
169        _ => {
170          error!("Received signal, but I don't have instructions what to do about it!");
171        }
172      }
173    }
174  }
175}
176
177
178/// Make sure that the loglevel is in color, even though not using pretty_env logger
179pub fn color_log(level : &Level) -> ColoredString {
180  match level {
181    Level::Error    => String::from(" ERROR!").red(),
182    Level::Warn     => String::from(" WARN  ").yellow(),
183    Level::Info     => String::from(" Info  ").green(),
184    Level::Debug    => String::from(" debug ").blue(),
185    Level::Trace    => String::from(" trace ").cyan(),
186  }
187}
188
189/// Set up the environmental (env) logger
190/// with our format
191///
192/// Ensure that the lines and module paths
193/// are printed in the logging output
194pub fn init_env_logger() {
195  env_logger::builder()
196    .format(|buf, record| {
197    writeln!( buf, "[{ts} - {level}][{module_path}:{line}] {args}",
198      ts    = Utc::now().format("%Y/%m/%d-%H:%M:%SUTC"), 
199      level = color_log(&record.level()),
200      module_path = record.module_path().unwrap_or("<unknown>"),
201      line  = record.line().unwrap_or(0),
202      args  = record.args()
203      )
204    }).init();
205}
206
207/// Keep track of run related statistics, errors
208#[derive(Debug, Copy, Clone)]
209pub struct RunStatistics {
210  /// The number of events we have recorded
211  pub n_events_rec      : usize,
212  /// The number of packets going through 
213  /// the event processing
214  pub evproc_npack      : usize,
215  /// The first event id we saw
216  pub first_evid        : u32,
217  /// The last event id we saw
218  pub last_evid         : u32,
219  /// The number of times we encountered 
220  /// a deserialization issue
221  pub n_err_deser       : usize,
222  /// The number of times we encountered 
223  /// an issue while sending over zmq
224  pub n_err_zmq_send    : usize,
225  /// The number of times we encountered
226  /// an issue with a wrong channel identifier
227  pub n_err_chid_wrong  : usize,
228  /// How many times did we read out an incorrect
229  /// tail?
230  pub n_err_tail_wrong  : usize,
231  /// The number of times we failed a crc32 check
232  pub n_err_crc32_wrong : usize,
233}
234
235impl RunStatistics {
236  
237  pub fn new() -> Self {
238    Self {
239      n_events_rec      : 0,
240      evproc_npack      : 0,
241      first_evid        : 0,
242      last_evid         : 0,
243      n_err_deser       : 0,
244      n_err_zmq_send    : 0,
245      n_err_chid_wrong  : 0,
246      n_err_tail_wrong  : 0,
247      n_err_crc32_wrong : 0,
248    }
249  }
250
251  pub fn get_n_anticipated(&self) -> i32 {
252    self.last_evid as i32 - self.first_evid as i32
253  }
254}
255
256impl fmt::Display for RunStatistics {
257  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258    let mut resp = String::from("<RunStatistics:\n");
259    resp += &(format!("  first event id : {}\n", self.first_evid));
260    resp += &(format!("  last  event id : {}\n", self.last_evid));
261    resp += &(format!("  --> expected {} event (ids)\n", self.get_n_anticipated()));
262    resp += &(format!("  event_processing #packets : {}\n", self.evproc_npack));
263    if self.get_n_anticipated() != self.evproc_npack as i32 {
264      resp += &(format!("  --> discrepancy of {} event (ids)\n", self.get_n_anticipated() - self.evproc_npack as i32))
265    }
266    resp += &(format!("  event_processing n tail err : {}\n", self.n_err_tail_wrong));
267    resp += &(format!("  event_processing n chid err : {}\n", self.n_err_chid_wrong));
268    write!(f, "{}", resp)
269  }
270}
271
272#[cfg(feature="database")]
273/// Sine fit without using external libraries
274pub fn fit_sine_sydney(volts: &Vec<f32>, times: &Vec<f32>) -> (f32, f32, f32) {
275  let start_bin = 20;
276  let size_bin = 900;
277  let pi = PI;
278  let mut data_size = 0;
279
280  let mut xi_yi = 0.0;
281  let mut xi_zi = 0.0;
282  let mut yi_zi = 0.0;
283  let mut xi_xi = 0.0;
284  let mut yi_yi = 0.0;
285  let mut xi_sum = 0.0;
286  let mut yi_sum = 0.0;
287  let mut zi_sum = 0.0;
288
289  for i in start_bin..(start_bin + size_bin) {
290      let xi = (2.0 * pi * 0.02 * times[i]).cos();
291      let yi = (2.0 * pi * 0.02 * times[i]).sin();
292      let zi = volts[i];
293
294      xi_yi += xi * yi;
295      xi_zi += xi * zi;
296      yi_zi += yi * zi;
297      xi_xi += xi * xi;
298      yi_yi += yi * yi;
299      xi_sum += xi;
300      yi_sum += yi;
301      zi_sum += zi;
302
303      data_size += 1;
304  }
305
306  let mut a_matrix = [[0.0; 3]; 3];
307  a_matrix[0][0] = xi_xi;
308  a_matrix[0][1] = xi_yi;
309  a_matrix[0][2] = xi_sum;
310  a_matrix[1][0] = xi_yi;
311  a_matrix[1][1] = yi_yi;
312  a_matrix[1][2] = yi_sum;
313  a_matrix[2][0] = xi_sum;
314  a_matrix[2][1] = yi_sum;
315  a_matrix[2][2] = data_size as f32;
316
317  let determinant = a_matrix[0][0] * a_matrix[1][1] * a_matrix[2][2]
318      + a_matrix[0][1] * a_matrix[1][2] * a_matrix[2][0]
319      + a_matrix[0][2] * a_matrix[1][0] * a_matrix[2][1]
320      - a_matrix[0][0] * a_matrix[1][2] * a_matrix[2][1]
321      - a_matrix[0][1] * a_matrix[1][0] * a_matrix[2][2]
322      - a_matrix[0][2] * a_matrix[1][1] * a_matrix[2][0];
323
324  let inverse_factor = 1.0 / determinant;
325
326  let mut cofactor_matrix = [[0.0; 3]; 3];
327  cofactor_matrix[0][0] = a_matrix[1][1] * a_matrix[2][2] - a_matrix[2][1] * a_matrix[1][2];
328  cofactor_matrix[0][1] = (a_matrix[1][0] * a_matrix[2][2] - a_matrix[2][0] * a_matrix[1][2]) * -1.0;
329  cofactor_matrix[0][2] = a_matrix[1][0] * a_matrix[2][1] - a_matrix[2][0] * a_matrix[1][1];
330  cofactor_matrix[1][0] = (a_matrix[0][1] * a_matrix[2][2] - a_matrix[2][1] * a_matrix[0][2]) * -1.0;
331  cofactor_matrix[1][1] = a_matrix[0][0] * a_matrix[2][2] - a_matrix[2][0] * a_matrix[0][2];
332  cofactor_matrix[1][2] = (a_matrix[0][0] * a_matrix[2][1] - a_matrix[2][0] * a_matrix[0][1]) * -1.0;
333  cofactor_matrix[2][0] = a_matrix[0][1] * a_matrix[1][2] - a_matrix[1][1] * a_matrix[0][2];
334  cofactor_matrix[2][1] = (a_matrix[0][0] * a_matrix[1][2] - a_matrix[1][0] * a_matrix[0][2]) * -1.0;
335  cofactor_matrix[2][2] = a_matrix[0][0] * a_matrix[1][1] - a_matrix[1][0] * a_matrix[0][1];
336
337  let mut inverse_matrix = [[0.0; 3]; 3];
338  for i in 0..3 {
339      for j in 0..3 {
340          inverse_matrix[i][j] = cofactor_matrix[j][i] * inverse_factor;
341      }
342  }
343
344  let p = [xi_zi, yi_zi, zi_sum];
345  let a = inverse_matrix[0][0] * p[0] + inverse_matrix[1][0] * p[1] + inverse_matrix[2][0] * p[2];
346  let b = inverse_matrix[0][1] * p[0] + inverse_matrix[1][1] * p[1] + inverse_matrix[2][1] * p[2];
347
348  let phi    = a.atan2(b);
349  let amp    = (a*a + b*b).sqrt();
350  let freq   = 0.02 as f32;
351
352  (amp, freq, phi)
353}
354
355//*************************************************
356// I/O - read/write (general purpose) files
357//
358//
359//pub fn read_value_from_file(file_path: &str) -> io::Result<u32> {
360//  let mut file = File::open(file_path)?;
361//  let mut contents = String::new();
362//  file.read_to_string(&mut contents)?;
363//  let value: u32 = contents.trim().parse().map_err(|err| {
364//    io::Error::new(io::ErrorKind::InvalidData, err)
365//  })?;
366//  Ok(value)
367//}
368
369/**************************************************/
370
371
372/// Helper function to generate a proper tcp string starting
373/// from the ip one.
374pub fn build_tcp_from_ip(ip: String, port: String) -> String {
375  //String::from("tcp://") + &ip + ":" + &port
376  format!("tcp://{}:{}", ip, port)
377}
378
379
380//**********************************************
381//
382// Analysis
383//
384
385/// Waveform analysis engine - identify waveform variables
386///
387/// This will populate the TofHits in an RBEvent
388///
389/// TofHits contain information about peak location,
390/// charge, timing.
391///
392/// FIXME - I think this should take a HashMap with 
393/// algorithm settings, which we can load from a 
394/// json file
395///
396/// # Arguments
397///
398/// * event       : current RBEvent with waveforms to 
399///                 work on
400/// * rb          : ReadoutBoard as loaded from the DB, 
401///                 with latest calibration attached
402/// * settings    : Parameters to configure the waveform
403///                 analysis & peak finding
404#[cfg(feature="database")]
405pub fn waveform_analysis(event         : &mut RBEvent,
406                         rb            : &ReadoutBoard,
407                         settings      : AnalysisEngineSettings)
408-> Result<(), AnalysisError> {
409  // Don't do analysis for mangled events!
410  if event.has_any_mangling_flag() {
411    warn!("Event for RB {} has data mangling! Not doing analysis!", rb.rb_id);
412    return Err(AnalysisError::DataMangling);
413  }
414  match event.self_check() {
415    Err(_err) => {
416      // Phlip want to ahve all hits even if they are broken
417    },
418    Ok(_)    => ()
419  }
420  let active_channels = event.header.get_channels();
421  // will become a parameter
422  let fit_sinus       = true;
423  // allocate memory for the calbration results
424  let mut voltages    : Vec<f32>= vec![0.0; NWORDS];
425  let mut times       : Vec<f32>= vec![0.0; NWORDS];
426
427  // Step 0 : If desired, fit sine
428  let mut fit_result = (0.0f32, 0.0f32, 0.0f32);
429  if fit_sinus {
430    if !active_channels.contains(&8) {
431      warn!("RB {} does not have ch9 data!", rb.rb_id);
432      //println!("{}", event.header);
433      return Err(AnalysisError::NoChannel9);
434    }
435    rb.calibration.voltages(9,
436                            event.header.stop_cell as usize,
437                            &event.adc[8],
438                            &mut voltages);
439    //warn!("We have to rework the spike cleaning!");
440    //match RBCalibrations::spike_cleaning(&mut ch_voltages,
441    //                                     event.header.stop_cell) {
442    //  Err(err) => {
443    //    error!("Spike cleaning failed! {err}");
444    //  }
445    //  Ok(_)    => ()
446    //}
447    rb.calibration.nanoseconds(9,
448                               event.header.stop_cell as usize,
449                               &mut times);
450    fit_result                = fit_sine_sydney(&voltages, &times);
451
452    //println!("FIT RESULT = {:?}", fit_result);
453    //event.header.set_sine_fit(fit_result);
454  }
455
456  // structure to store final result
457  // extend with Vec<TofHit> in case
458  // we want to have multiple hits
459  let mut paddles    = HashMap::<u8, TofHit>::new();
460  //println!("RBID {}, Paddles {:?}", rb.rb_id ,rb.get_paddle_ids());
461  for pid in rb.get_paddle_ids() {
462    // cant' fail by constructon of pid
463    let ch_a = rb.get_pid_rbchA(pid).unwrap() as usize;
464    let ch_b = rb.get_pid_rbchB(pid).unwrap() as usize;
465    let mut hit = TofHit::new();
466    hit.paddle_id = pid;
467    //println!("{ch_a}, {ch_b}, active_channels {:?}", active_channels);
468    for (k, ch) in [ch_a, ch_b].iter().enumerate() {
469      // Step 1: Calibration
470      //println!("Ch {}, event {}", ch, event);
471      //println!("---------------------------");
472      //println!("pid {}, active channels : {:?}, ch {}",pid, active_channels, ch);
473      if !active_channels.contains(&(*ch as u8 -1)) {
474        trace!("Skipping channel {} because it is not marked to be readout in the event header channel mask!", ch);
475        continue;
476      }
477      //println!("Will do waveform analysis for ch {}", ch);
478      rb.calibration.voltages(*ch,
479                              event.header.stop_cell as usize,
480                              &event.adc[*ch as usize -1],
481                              &mut voltages);
482      //FIXME - spike cleaning!
483      //match RBCalibrations::spike_cleaning(&mut ch_voltages,
484      //                                     event.header.stop_cell) {
485      //  Err(err) => {
486      //    error!("Spike cleaning failed! {err}");
487      //  }
488      //  Ok(_)    => ()
489      //}
490      rb.calibration.nanoseconds(*ch,
491                                 event.header.stop_cell as usize,
492                                 &mut times);
493      // Step 2: Pedestal subtraction
494      let (ped, ped_err) = calculate_pedestal(&voltages,
495                                              settings.pedestal_thresh,
496                                              settings.pedestal_begin_bin,
497                                              settings.pedestal_win_bins);
498      trace!("Calculated pedestal of {} +- {}", ped, ped_err);
499      for n in 0..voltages.len() {
500        voltages[n] -= ped;
501      }
502      let mut charge : f32 = 0.0;
503      //let peaks : Vec::<(usize, usize)>;
504      let mut cfd_times = Vec::<f32>::new();
505      let mut max_volts = 0.0f32;
506      // Step 4 : Find peaks
507      // FIXME - what do we do for multiple peaks?
508      // Currently we basically throw them away
509      match find_peaks(&voltages ,
510                       &times    ,
511                       settings.find_pks_t_start , 
512                       settings.find_pks_t_window,
513                       settings.min_peak_size    ,
514                       settings.find_pks_thresh  ,
515                       settings.max_peaks      ) {
516        Err(err) => {
517          // FIXME - if this happens, most likely the channel is dead. 
518          debug!("Unable to find peaks for RB{:02} ch {ch}! Ignoring this channel!", rb.rb_id);
519          debug!("We won't be able to calculate timing information for this channel! Err {err}");
520        },
521        Ok(peaks)  => {
522          //peaks = pks;
523          // Step 5 : Find tdcs
524          //println!("Found {} peaks for ch {}! {:?}", peaks.len(), raw_ch, peaks);
525          for pk in peaks.iter() {
526            match cfd_simple(&voltages,
527                             &times,
528                             settings.cfd_fraction,
529                             pk.0, pk.1) {
530              Err(err) => {
531                debug!("Unable to calculate cfd for peak {} {}! {}", pk.0, pk.1, err);
532              }
533              Ok(cfd) => {
534                cfd_times.push(cfd);
535              }
536            }
537            let pk_height = voltages[pk.0..pk.1].iter().max_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less)).unwrap(); 
538            max_volts = *pk_height;
539            let max_index = voltages.iter().position(|element| *element == max_volts).unwrap();
540
541            let (start_q_int, stop_q_int) = if max_index - 40 < 10 {
542              (10, 210)
543            } else {
544              (max_index - 40, max_index + 160)
545            };
546          
547
548            //debug!("Check impedance value! Just using 50 [Ohm]");
549            // Step 3 : charge integration
550            // FIXME - make impedance a settings parameter
551            match integrate(&voltages,
552                            &times,
553                            //settings.integration_start,
554                            //settings.integration_window,
555                            //pk.0, 
556                            //pk.1,
557                            start_q_int,
558                            stop_q_int,
559                            50.0) {
560              Err(err) => {
561                error!("Integration failed! Err {err}");
562              }
563              Ok(chrg)   => {
564                charge = chrg;
565              }
566            }
567            // // just do the first peak for now
568            // let pk_height = voltages[pk.0..pk.1].iter().max_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less)).unwrap(); 
569            // max_volts = *pk_height; 
570            // //debug!("Check impedance value! Just using 50 [Ohm]");
571            // // Step 3 : charge integration
572            // // FIXME - make impedance a settings parameter
573            // match integrate(&voltages,
574            //                 &times,
575            //                 //settings.integration_start,
576            //                 //settings.integration_window,
577            //                 pk.0, 
578            //                 pk.1,
579            //                 50.0) {
580            //   Err(err) => {
581            //     error!("Integration failed! Err {err}");
582            //   }
583              
584            break;
585          }
586        }// end OK
587      } // end match find_peaks 
588      let mut tdc : f32 = 0.0; 
589      if cfd_times.len() > 0 {
590        tdc = cfd_times[0];
591      }
592      //println!("Calucalated tdc {}, charge {}, max {} for ch {}!", tdc, charge, max_volts, ch); 
593      //if rb.channel_to_paddle_end_id[*raw_ch as usize] > 2000 {
594      if k == 0 {
595        hit.ftime_a      = tdc;
596        hit.fpeak_a      = max_volts;
597        hit.set_time_a(tdc);
598        hit.set_charge_a(charge);
599        hit.set_peak_a(max_volts);
600        hit.baseline_a     = f16::from_f32(ped);
601        hit.baseline_a_rms = f16::from_f32(ped_err);
602      } else {
603        hit.ftime_b = tdc;
604        hit.fpeak_b = max_volts;
605        hit.set_time_b(tdc);
606        hit.set_charge_b(charge);
607        hit.set_peak_b(max_volts);
608        hit.baseline_b     = f16::from_f32(ped);
609        hit.baseline_b_rms = f16::from_f32(ped_err);
610        // this is the seoond iteration,
611        // we are done!
612        hit.phase = f16::from_f32(fit_result.2);
613        paddles.insert(pid, hit);
614      }
615    }
616  }
617  let result = paddles.into_values().collect();
618  event.hits = result;
619  //print ("EVENT {}", event);
620  Ok(())
621}
622
623//**********************************************
624
625/// Load the rb channel vs paddle end id mapping
626///
627/// The map file is expected to have information for 
628/// all rbs, rb_id is used to grab the section for 
629/// the specific rb.
630pub fn get_rb_ch_pid_map(map_file : PathBuf, rb_id : u8) -> RBChannelPaddleEndIDMap {
631  let mut mapping = RBChannelPaddleEndIDMap::new();
632  let json_content : String;
633  match read_to_string(&map_file) {
634    Ok(_json_content) => {
635      json_content = _json_content;
636    },
637    Err(err) => { 
638      error!("Unable to parse json file {}. Error {err}", map_file.display());
639      return mapping;
640    }      
641  }
642  let json : Value;
643  match serde_json::from_str(&json_content) {
644    Ok(_json) => {
645      json = _json;
646    },
647    Err(err) => { 
648      error!("Unable to parse json file {}. Error {err}", map_file.display());
649      return mapping;
650    }
651  }
652  for ch in 0..8 {
653    let tmp_val = &json[rb_id.to_string()][(ch +1).to_string()];
654    let val = tmp_val.to_string().parse::<u16>().unwrap_or(0);
655    mapping.insert(ch as u8 + 1, val);
656  }
657  mapping
658}
659
660pub fn get_ltb_dsi_j_ch_mapping(mapping_file : PathBuf) -> DsiLtbRBMapping {
661  let mut mapping = HashMap::<u8,HashMap::<u8,HashMap::<u8,(u8,u8)>>>::new();
662  for dsi in 1..6 {
663    mapping.insert(dsi, HashMap::<u8,HashMap::<u8, (u8, u8)>>::new());
664    for j in 1..6 {
665      mapping.get_mut(&dsi).unwrap().insert(j, HashMap::<u8,(u8, u8)>::new());
666      for ch in 1..17 {
667        mapping.get_mut(&dsi).unwrap().get_mut(&j).unwrap().insert(ch, (0,0));
668      }
669    }
670  }
671  let json_content : String;
672  match read_to_string(&mapping_file) {
673    Ok(_json_content) => {
674      json_content = _json_content;
675    },
676    Err(err) => { 
677      error!("Unable to parse json file {}. Error {err}", mapping_file.display());
678      return mapping;
679    }      
680  }
681  let json : Value;
682  match serde_json::from_str(&json_content) {
683    Ok(_json) => {
684      json = _json;
685    },
686    Err(err) => { 
687      error!("Unable to parse json file {}. Error {err}", mapping_file.display());
688      return mapping;
689    }
690  }
691  for dsi in 1..6 { 
692    for j in 1..6 {
693      for ch in 1..17 {
694        let val = mapping.get_mut(&dsi).unwrap().get_mut(&j).unwrap().get_mut(&ch).unwrap();
695        //println!("Checking {} {} {}", dsi, j, ch);
696        let tmp_val = &json[dsi.to_string()][j.to_string()][ch.to_string()];
697        *val = (tmp_val[0].to_string().parse::<u8>().unwrap_or(0), tmp_val[1].to_string().parse::<u8>().unwrap_or(0));
698      }
699    }
700  }
701  debug!("Mapping {:?}", mapping);
702  mapping
703}
704
705/// Convert an int value to the board ID string.
706pub fn to_board_id_string(rb_id: u32) -> String {
707
708  //String::from("RB") + &format!("{:02}", rb_id)
709  format!("RB{:02}", rb_id)
710}
711
712/**********************************************************/
713
714#[derive(Debug, Clone, Args, PartialEq)]
715pub struct LtbThresholdOpts {
716  /// ID of the LTB to target
717  #[arg(short, long, default_value_t = DEFAULT_LTB_ID)]
718  pub id: u8,
719  /// Name of the threshold to be set
720  #[arg(required = true)]
721  pub name: LTBThresholdName,
722  /// Threshold level to be set
723  #[arg(required = true)]
724  pub level: u16
725}
726
727impl LtbThresholdOpts {
728  pub fn new(id: u8, name: LTBThresholdName, level: u16) -> Self {
729    Self { 
730      id,
731      name,
732      level
733    }
734  }
735}
736
737// repr is u16 in order to leave room for preamp bias
738#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize, clap::ValueEnum)]
739#[repr(u8)]
740pub enum LTBThresholdName {
741  Unknown  = 0u8,
742  Hit      = 10u8,
743  Beta     = 20u8,
744  Veto     = 30u8,
745}
746
747impl LTBThresholdName {
748  pub fn get_ch_number(threshold_name: LTBThresholdName) -> Result<u8, SetError> {
749    match threshold_name {
750      LTBThresholdName::Hit     => Ok(0u8),
751      LTBThresholdName::Beta    => Ok(1u8),
752      LTBThresholdName::Veto    => Ok(2u8),
753      LTBThresholdName::Unknown => {
754        error!("Not able to get a LTB threshold from Unknown");
755        Err(SetError::EmptyInputData)
756      }
757    }
758  }
759}
760
761impl fmt::Display for LTBThresholdName {
762  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
763    let r = serde_json::to_string(self).unwrap_or(
764      String::from("Error: cannot unwrap this PowerStatusEnum"));
765    write!(f, "<PowerStatusEnum: {}>", r)
766  }
767}
768
769impl From<u8> for LTBThresholdName {
770  fn from(value: u8) -> Self {
771    match value {
772      0u8  => LTBThresholdName::Unknown,
773      10u8 => LTBThresholdName::Hit,
774      20u8 => LTBThresholdName::Beta,
775      30u8 => LTBThresholdName::Veto,
776      _    => LTBThresholdName::Unknown
777    }
778  }
779}
780
781#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize, clap::ValueEnum)]
782#[repr(u8)]
783pub enum TofComponent {
784  Unknown   = 0u8,
785  /// everything (LTB + preamps + MT)
786  All       = 1u8,
787  /// everything but MT (LTB + preamps)
788  AllButMT  = 2u8,
789  /// TOF CPU
790  TofCpu    = 3u8,
791  /// MT alone
792  MT        = 10u8,
793  /// all or specific RBs
794  RB        = 20u8,
795  /// all or specific PBs
796  PB        = 30u8,
797  /// all or specific LTBs
798  LTB       = 40u8,
799  /// all or specific preamp
800  Preamp    = 50u8
801}
802
803impl fmt::Display for TofComponent {
804  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
805    let r = serde_json::to_string(self).unwrap_or(
806      String::from("Error: cannot unwrap this TofComponent"));
807    write!(f, "<TofComponent: {}>", r)
808  }
809}
810
811impl From<u8> for TofComponent {
812  fn from(value: u8) -> Self {
813    match value {
814      0u8  => TofComponent::Unknown,
815      1u8  => TofComponent::All,
816      2u8  => TofComponent::AllButMT,
817      3u8  => TofComponent::TofCpu,
818      10u8 => TofComponent::MT,
819      20u8 => TofComponent::RB,
820      30u8 => TofComponent::PB,
821      40u8 => TofComponent::LTB,
822      50u8 => TofComponent::Preamp,
823      _    => TofComponent::Unknown
824    }
825  }
826}
827
828impl From<TofComponent> for clap::builder::Str {
829  fn from(value: TofComponent) -> Self {
830    match value {
831      TofComponent::Unknown  => clap::builder::Str::from("Unknown"),
832      TofComponent::All      => clap::builder::Str::from("All"),
833      TofComponent::AllButMT => clap::builder::Str::from("AllButMT"),
834      TofComponent::TofCpu   => clap::builder::Str::from("TofCpu"),
835      TofComponent::MT       => clap::builder::Str::from("MT"),
836      TofComponent::RB       => clap::builder::Str::from("RB"),
837      TofComponent::PB       => clap::builder::Str::from("PB"),
838      TofComponent::LTB      => clap::builder::Str::from("LTB"),
839      TofComponent::Preamp   => clap::builder::Str::from("Preamp")
840    }
841  }
842}
843