tof_dataclasses/events/
master_trigger.rs

1//! MasterTriggerEvent
2#[cfg(feature = "pybindings")]
3use pyo3::pyclass;
4
5cfg_if::cfg_if! {
6  if #[cfg(feature = "random")]  {
7    use crate::FromRandom;
8    extern crate rand;
9    use rand::Rng;
10  }
11}
12
13use std::fmt;
14//use std::time::Duration;
15//use std::collections::HashMap;
16
17use crate::serialization::{
18    Serialization,
19    Packable,
20    SerializationError,
21    search_for_u16,
22    parse_u8,
23    parse_u16,
24    parse_u32,
25    parse_u64,
26};
27
28use crate::packets::{
29  PacketType
30};
31
32//use crate::DsiLtbRBMapping;
33use crate::events::{
34  EventStatus,
35  TofEventSummary,
36  transcode_trigger_sources,
37};
38
39// A comment about the GAPS (antiparticle) trigger
40//
41//  The default values used where thus:
42//  INNER_TOF_THRESH = 3
43//  OUTER_TOF_THRESH = 3
44//  TOTAL_TOF_THRESH = 8
45//  REQUIRE_BETA =1
46//
47//  so this corresponds to the BETA being set (required) and the loose settings for the number of hits.
48//  Is this correct?
49//
50//  If so, at some point (not yet because we are not getting data through the system), I'd like us to run
51//  for a while with these three settings:
52//
53//  INNER_TOF_THRESH = 3
54//  OUTER_TOF_THRESH = 3
55//  TOTAL_TOF_THRESH =8
56//  REQUIRE_BETA =1
57//
58//  INNER_TOF_THRESH = 3
59//  OUTER_TOF_THRESH = 3
60//  TOTAL_TOF_THRESH =8
61//  REQUIRE_BETA =0
62//
63//  INNER_TOF_THRESH = 0
64//  OUTER_TOF_THRESH = 0
65//  TOTAL_TOF_THRESH =0
66//  REQUIRE_BETA =1
67//
68//  This is from Andrew's email about Philip's debugging triggers:
69//  I am proposing to just add a single new trigger, which is configured by:
70//  
71//  cube_side_thresh   
72//  cube_top_thresh    
73//  cube_bot_thresh    
74//  cube_corner_thresh 
75//  umbrella_thresh    
76//  cortina_thresh     
77//  inner_tof_thresh 
78//  outer_tof_thresh
79//  total_tof_thresh 
80//  
81//  The trigger is just
82//  
83//  cube_side_cnt >= cube_side_thresh AND cube_top_cnt >= cube_top_thresh AND .... etc.
84//  
85//  So setting thresh to zero disables a condition, and should let you implement any of these combinations except 3, which would need some new parameter.
86
87
88/// masks to decode LTB hit masks
89pub const LTB_CH0 : u16 = 0x3   ;
90pub const LTB_CH1 : u16 = 0xc   ;
91pub const LTB_CH2 : u16 = 0x30  ; 
92pub const LTB_CH3 : u16 = 0xc0  ;
93pub const LTB_CH4 : u16 = 0x300 ;
94pub const LTB_CH5 : u16 = 0xc00 ;
95pub const LTB_CH6 : u16 = 0x3000;
96pub const LTB_CH7 : u16 = 0xc000;
97pub const LTB_CHANNELS : [u16;8] = [
98    LTB_CH0,
99    LTB_CH1,
100    LTB_CH2,
101    LTB_CH3,
102    LTB_CH4,
103    LTB_CH5,
104    LTB_CH6,
105    LTB_CH7
106];
107
108///// Combine 32 + 16bit timestamp to 48 bit timestamp
109//fn timestamp48(bits32 : u32, bits16 : u16) -> u64 {
110//  (bits32 as u64) << 16 | bits 16 as u64 
111//}
112
113
114#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
115#[repr(u8)]
116#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
117pub enum TriggerType {
118  Unknown         = 0u8,
119  /// -> 1-10 "pysics" triggers
120  Any             = 1u8,
121  Track           = 2u8,
122  TrackCentral    = 3u8,
123  Gaps            = 4u8,
124  Gaps633         = 5u8, 
125  Gaps422         = 6u8,
126  Gaps211         = 7u8,
127  TrackUmbCentral = 8u8,
128  /// -> 20+ "Philip's triggers"
129  /// Any paddle HIT in UMB  + any paddle HIT in CUB
130  UmbCube         = 21u8,
131  /// Any paddle HIT in UMB + any paddle HIT in CUB top
132  UmbCubeZ        = 22u8,
133  /// Any paddle HIT in UMB + any paddle hit in COR + any paddle hit in CUB 
134  UmbCorCube      = 23u8,
135  /// Any paddle HIT in COR + any paddle HIT in CUB SIDES
136  CorCubeSide     = 24u8,
137  /// Any paddle hit in UMB + any three paddles HIT in CUB
138  Umb3Cube        = 25u8,
139  /// > 100 -> Debug triggers
140  Poisson         = 100u8,
141  Forced          = 101u8,
142  FixedRate       = 102u8,
143  /// > 200 -> These triggers can not be set, they are merely
144  /// the result of what we read out from the trigger mask of 
145  /// the ltb
146  ConfigurableTrigger = 200u8,
147}
148
149impl fmt::Display for TriggerType {
150  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151    let r = serde_json::to_string(self).unwrap_or(
152      String::from("ERROR: DeserializationError!"));
153    write!(f, "<TriggerType: {}>", r)
154  }
155}
156
157impl TriggerType {
158  pub fn to_u8(&self) -> u8 {
159    match self {
160      TriggerType::Unknown => {
161        return 0;
162      }
163      TriggerType::Poisson => {
164        return 100;
165      }
166      TriggerType::Forced => {
167        return 101;
168      }
169      TriggerType::FixedRate => {
170        return 102;
171      }
172      TriggerType::Any => {
173        return 1;
174      }
175      TriggerType::Track => {
176        return 2;
177      }
178      TriggerType::TrackCentral => {
179        return 3;
180      }
181      TriggerType::Gaps => {
182        return 4;
183      }
184      TriggerType::Gaps633 => {
185        return 5;
186      }
187      TriggerType::Gaps422 => {
188        return 6;
189      }
190      TriggerType::Gaps211 => {
191        return 7;
192      }
193      TriggerType::TrackUmbCentral => {
194        return 8;
195      }
196      TriggerType::UmbCube => {
197        return 21;
198      }
199      TriggerType::UmbCubeZ => {
200        return 22; 
201      }
202      TriggerType::UmbCorCube => {
203        return 23;
204      }
205      TriggerType::CorCubeSide => {
206        return 24;
207      }
208      TriggerType::Umb3Cube => {
209        return 25;
210      }
211      TriggerType::ConfigurableTrigger => {
212        return 200;  
213      }
214    }
215  }
216}
217
218impl From<u8> for TriggerType {
219  fn from(value: u8) -> Self {
220    match value {
221      0   => TriggerType::Unknown,
222      100 => TriggerType::Poisson,
223      101 => TriggerType::Forced,
224      102 => TriggerType::FixedRate,
225      1   => TriggerType::Any,
226      2   => TriggerType::Track,
227      3   => TriggerType::TrackCentral,
228      4   => TriggerType::Gaps,
229      5   => TriggerType::Gaps633,
230      6   => TriggerType::Gaps422,
231      7   => TriggerType::Gaps211,
232      8   => TriggerType::TrackUmbCentral,
233      21  => TriggerType::UmbCube,
234      22  => TriggerType::UmbCubeZ,
235      23  => TriggerType::UmbCorCube,
236      24  => TriggerType::CorCubeSide,
237      25  => TriggerType::Umb3Cube,
238      200 => TriggerType::ConfigurableTrigger,
239      _   => TriggerType::Unknown
240    }
241  }
242}
243
244#[cfg(feature = "random")]
245impl FromRandom for TriggerType {
246  
247  fn from_random() -> Self {
248    let choices = [
249      TriggerType::Unknown,
250      TriggerType::Poisson,
251      TriggerType::Forced,
252      TriggerType::FixedRate,
253      TriggerType::Any,
254      TriggerType::Track,
255      TriggerType::TrackCentral,
256      TriggerType::Gaps,
257      TriggerType::Gaps633,
258      TriggerType::Gaps422,
259      TriggerType::Gaps211,
260      TriggerType::TrackUmbCentral,
261      TriggerType::UmbCube,
262      TriggerType::UmbCubeZ,
263      TriggerType::UmbCorCube,
264      TriggerType::CorCubeSide,
265      TriggerType::Umb3Cube,
266      TriggerType::ConfigurableTrigger,
267    ];
268    let mut rng  = rand::thread_rng();
269    let idx = rng.gen_range(0..choices.len());
270    choices[idx]
271  }
272}
273
274/////////////////////////////////////////////////
275
276/// LTB Thresholds as passed on by the MTB
277/// [See also](https://gaps1.astro.ucla.edu/wiki/gaps/images/gaps/5/52/LTB_Data_Format.pdf)
278#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
279#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
280#[repr(u8)]
281pub enum LTBThreshold {
282  NoHit = 0u8,
283  /// First threshold, 40mV, about 0.75 minI
284  Hit   = 1u8,
285  /// Second threshold, 32mV (? error in doc ?, about 2.5 minI
286  Beta  = 2u8,
287  /// Third threshold, 375mV about 30 minI
288  Veto  = 3u8,
289  /// Use u8::MAX for Unknown, since 0 is pre-determined for 
290  /// "NoHit, 
291  Unknown = 255u8
292}
293
294impl fmt::Display for LTBThreshold {
295  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296    let r = serde_json::to_string(self).unwrap_or(
297      String::from("ERROR: DeserializationError!"));
298    write!(f, "<LTBThreshold: {}>", r)
299  }
300}
301
302impl From<u8> for LTBThreshold {
303  fn from(value: u8) -> Self {
304    match value {
305      0 => LTBThreshold::NoHit,
306      1 => LTBThreshold::Hit,
307      2 => LTBThreshold::Beta,
308      3 => LTBThreshold::Veto,
309      _ => LTBThreshold::Unknown
310    }
311  }
312}
313
314#[cfg(feature = "random")]
315impl FromRandom for LTBThreshold {
316  
317  fn from_random() -> Self {
318    let choices = [
319      LTBThreshold::NoHit,
320      LTBThreshold::Hit,
321      LTBThreshold::Beta,
322      LTBThreshold::Veto,
323      LTBThreshold::Unknown
324    ];
325    let mut rng  = rand::thread_rng();
326    let idx = rng.gen_range(0..choices.len());
327    choices[idx]
328  }
329}
330
331/////////////////////////////////////////////////
332
333/// An event as observed by the MTB
334///
335/// This is condensed to the most 
336/// crucial information 
337///
338/// FIXME : implementation of absolute time
339#[derive(Debug, Clone, PartialEq)]
340pub struct MasterTriggerEvent {
341  pub event_status   : EventStatus,
342  pub event_id       : u32,
343  /// Internal timestamp at the time of trigger (1 unit = 10 ns)
344  /// Free running counter, rolling over every ~42 seconds
345  pub timestamp      : u32,
346  /// Timestamp at the edge of the TIU GPS (1 unit = 10 ns)
347  pub tiu_timestamp  : u32,
348  /// Second received from the TIU (format?) 
349  pub tiu_gps32      : u32,
350  pub tiu_gps16      : u16,
351  pub crc            : u32,
352  // NEW - change/extension in API for MTB fw >= 3.0.0
353  /// Trigger source:  
354  pub trigger_source : u16,
355  pub dsi_j_mask     : u32,
356  pub channel_mask   : Vec<u16>,
357  pub mtb_link_mask  : u64,
358}
359
360impl MasterTriggerEvent {
361  /// Implementation version, might roughly 
362  /// correspond to fw version
363  pub const VERSION : u8 = 3;
364
365  pub fn new() -> Self {
366    Self { 
367      event_status   : EventStatus::Unknown,
368      event_id       : 0,
369      timestamp      : 0,
370      tiu_timestamp  : 0,
371      tiu_gps32      : 0,
372      tiu_gps16      : 0,
373      crc            : 0,
374      trigger_source : 0,
375      dsi_j_mask     : 0,
376      channel_mask   : Vec::<u16>::new(),
377      mtb_link_mask  : 0,
378    }   
379  }
380
381  /// Get the RB link IDs according to the mask
382  pub fn get_rb_link_ids(&self) -> Vec<u8> {
383    let mut links = Vec::<u8>::new();
384    for k in 0..64 {
385      if (self.mtb_link_mask >> k) as u64 & 0x1 == 1 {
386        links.push(k as u8);
387      }
388    }
389    links
390  }
391
392  /// Get the combination of triggered DSI/J/CH on 
393  /// the MTB which formed the trigger. This does 
394  /// not include further hits which fall into the 
395  /// integration window. For those, se rb_link_mask
396  ///
397  /// The returned values follow the TOF convention
398  /// to start with 1, so that we can use them to 
399  /// look up LTB ids in the db.
400  ///
401  /// # Returns
402  ///
403  ///   Vec<(hit)> where hit is (DSI, J, (CH,CH), threshold) 
404  pub fn get_trigger_hits(&self) -> Vec<(u8, u8, (u8, u8), LTBThreshold)> {
405    let mut hits = Vec::<(u8,u8,(u8,u8),LTBThreshold)>::with_capacity(5); 
406    let physical_channels = [(1u8,  2u8), (3u8,4u8), (5u8, 6u8), (7u8, 8u8),
407                             (9u8, 10u8), (11u8,12u8), (13u8, 14u8), (15u8, 16u8)];
408    //let n_masks_needed = self.dsi_j_mask.count_ones() / 2 + self.dsi_j_mask.count_ones() % 2;
409    let n_masks_needed = self.dsi_j_mask.count_ones();
410    if self.channel_mask.len() < n_masks_needed as usize {
411      error!("We need {} hit masks, but only have {}! This is bad!", n_masks_needed, self.channel_mask.len());
412      return hits;
413    }
414    let mut n_mask = 0;
415    trace!("Expecting {} hit masks", n_masks_needed);
416    trace!("ltb channels {:?}", self.dsi_j_mask);
417    trace!("hit masks {:?}", self.channel_mask); 
418    //println!("We see LTB Channels {:?} with Hit masks {:?} for {} masks requested by us!", self.dsi_j_mask, self.channel_mask, n_masks_needed);
419    
420    // one k here is for one ltb
421    for k in 0..32 {
422      if (self.dsi_j_mask >> k) as u32 & 0x1 == 1 {
423        let mut dsi = 0u8;
424        let mut j   = 0u8;
425        if k < 5 {
426          dsi = 1;
427          j   = k as u8 + 1;
428        } else if k < 10 {
429          dsi = 2;
430          j   = k as u8 - 5 + 1;
431        } else if k < 15 {
432          dsi = 3;
433          j   = k as u8- 10 + 1;
434        } else if k < 20 {
435          dsi = 4;
436          j   = k as u8- 15 + 1;
437        } else if k < 25 {
438          dsi = 5;
439          j   = k as u8 - 20 + 1;
440        } 
441        //let dsi = (k as f32 / 4.0).floor() as u8 + 1;       
442        //let j   = (k % 5) as u8 + 1;
443        //println!("n_mask {n_mask}");
444        let channels = self.channel_mask[n_mask]; 
445        for (i,ch) in LTB_CHANNELS.iter().enumerate() {
446          //let chn = *ch as u8 + 1;
447          let ph_chn = physical_channels[i];
448          //let chn = i as u8 + 1;
449          //println!("i,ch {}, {}", i, ch);
450          let thresh_bits = ((channels & ch) >> (i*2)) as u8;
451          //println!("thresh_bits {}", thresh_bits);
452          if thresh_bits > 0 { // hit over threshold
453            hits.push((dsi, j, ph_chn, LTBThreshold::from(thresh_bits)));
454          }
455        }
456        n_mask += 1;
457      } // next ltb
458    }
459    hits
460  }
461
462  ///// Compatibility with older data.
463  ///// Convert deprecated array type format
464  ///// to new system
465  //fn get_dsi_j_mask_from_old_data(&mut self, mask : u32) {
466  //  // if I am not completly mistaken, this can be saved 
467  //  // directly
468  //  self.dsi_j_mask = mask;
469  //}
470
471  ///// Compatiblity with older data.
472  ///// Convert deprecated array type format
473  ///// to new system
474  //fn get_channel_mask_from_old_data(&mut self, mask : u32) {
475  //  self.channel_mask.push(mask as u16); 
476  //}
477
478  /// combine the tiu gps 16 and 32bit timestamps 
479  /// into a 48bit timestamp
480  #[deprecated(since = "0.10.3", note = "The timestamp of the gs is simply only 32 bits")]
481  pub fn get_timestamp_gps48(&self) -> u64 {
482    ((self.tiu_gps16 as u64) << 32) | self.tiu_gps32 as u64 
483  }
484
485  pub fn get_timestamp_gps(&self) -> u32 {
486    self.tiu_gps32
487  }
488 
489
490  /// Get absolute timestamp as sent by the GPS
491  pub fn get_timestamp_abs48(&self) -> u64 {
492    let gps = self.get_timestamp_gps() as u64;
493    let mut timestamp = self.timestamp as u64;
494    if timestamp < self.tiu_timestamp as u64 {
495      // it has wrapped
496      timestamp += u32::MAX as u64 + 1;
497    }
498    let gps_mult = match 100_000_000u64.checked_mul(gps) {
499    //let gps_mult = match 100_000u64.checked_mul(gps) {
500      Some(result) => result,
501      None => {
502          // Handle overflow case here
503          // Example: log an error, return a default value, etc.
504          0 // Example fallback value
505      }
506    };
507  
508    let ts = gps_mult + (timestamp - self.tiu_timestamp as u64);
509    ts
510  }
511
512  /// Get the trigger sources from trigger source byte
513  /// In case of the custom (configurable triggers, this
514  ///
515  /// will only return "ConfigurableTrigger" since the 
516  /// MTB does not know about these triggers as individual
517  /// types
518  pub fn get_trigger_sources(&self) -> Vec<TriggerType> {
519    transcode_trigger_sources(self.trigger_source)
520  }
521
522  /// Returns the trigger types which have to be defined as "global"
523  ///
524  /// Global triggers will force a readout of all panels and can 
525  /// be operated in conjuction with the set trigger
526  pub fn get_global_trigger_soures(&self) -> Vec<TriggerType> {
527    let mut t_types = Vec::<TriggerType>::new();
528
529    let track_umb_central_trigger = self.trigger_source >> 11 & 0x1 == 1;
530    if track_umb_central_trigger{
531      t_types.push(TriggerType::TrackUmbCentral);
532    }
533    let central_track_trigger
534                       = self.trigger_source >> 13 & 0x1 == 1;
535    if central_track_trigger {
536      t_types.push(TriggerType::TrackCentral);
537    }
538    let track_trigger  = self.trigger_source >> 14 & 0x1 == 1;
539    if track_trigger {
540      t_types.push(TriggerType::Track);
541    }
542    let any_trigger    = self.trigger_source >> 15 & 0x1 == 1;
543    if any_trigger {
544      t_types.push(TriggerType::Any);
545    }
546    t_types
547  }
548
549  pub fn is_trace_suppressed(&self) -> bool {
550    let is_trace_suppressed = self.trigger_source >> 12 & 0x1 == 1;
551    is_trace_suppressed
552  }
553}
554
555impl Packable for MasterTriggerEvent {
556  const PACKET_TYPE : PacketType = PacketType::MasterTrigger;
557}
558
559
560impl Serialization for MasterTriggerEvent {
561  
562  /// Variable size
563  const SIZE : usize = 0;
564  const TAIL : u16   = 0x5555;
565  const HEAD : u16   = 0xAAAA;
566
567  fn to_bytestream(&self) -> Vec::<u8> {
568    let mut bs = Vec::<u8>::with_capacity(MasterTriggerEvent::SIZE);
569    bs.extend_from_slice(&MasterTriggerEvent::HEAD.to_le_bytes());
570    bs.push(self.event_status as u8);
571    bs.extend_from_slice(&self.event_id.to_le_bytes()); 
572    bs.extend_from_slice(&self.timestamp.to_le_bytes());
573    bs.extend_from_slice(&self.tiu_timestamp.to_le_bytes());
574    bs.extend_from_slice(&self.tiu_gps32.to_le_bytes());
575    bs.extend_from_slice(&self.tiu_gps16.to_le_bytes());
576    bs.extend_from_slice(&self.crc.to_le_bytes());
577    bs.extend_from_slice(&self.trigger_source.to_le_bytes());
578    bs.extend_from_slice(&self.dsi_j_mask.to_le_bytes());
579    let n_channel_masks = self.channel_mask.len();
580    bs.push(n_channel_masks as u8);
581    for k in 0..n_channel_masks {
582      bs.extend_from_slice(&self.channel_mask[k].to_le_bytes());
583    }
584    bs.extend_from_slice(&self.mtb_link_mask.to_le_bytes());
585    bs.extend_from_slice(&MasterTriggerEvent::TAIL.to_le_bytes());
586    bs
587  }
588
589  fn from_bytestream(stream : &Vec<u8>,
590                     pos    : &mut usize)
591    -> Result<Self, SerializationError> {
592    let mut mt = Self::new();
593    let header = parse_u16(stream, pos); 
594    if header != Self::HEAD {
595      return Err(SerializationError::HeadInvalid);
596    }
597    mt.event_status       = parse_u8 (stream, pos).into();
598    mt.event_id           = parse_u32(stream, pos);
599    mt.timestamp          = parse_u32(stream, pos);
600    mt.tiu_timestamp      = parse_u32(stream, pos);
601    mt.tiu_gps32          = parse_u32(stream, pos);
602    mt.tiu_gps16          = parse_u16(stream, pos);
603    mt.crc                = parse_u32(stream, pos);
604    mt.trigger_source     = parse_u16(stream, pos);
605    mt.dsi_j_mask         = parse_u32(stream, pos);
606    let n_channel_masks   = parse_u8(stream, pos);
607    for _ in 0..n_channel_masks {
608      mt.channel_mask.push(parse_u16(stream, pos));
609    }
610    mt.mtb_link_mask      = parse_u64(stream, pos);
611    let tail              = parse_u16(stream, pos);
612    if tail != Self::TAIL {
613      error!("Invalid tail signature {}!", tail);
614      mt.event_status = EventStatus::TailWrong;
615      // PATCH - if this is old data, just skip it and
616      // search the next tail
617      match search_for_u16(Self::TAIL, stream, *pos) {
618        Ok(tail_pos) => {
619          error!("The tail was invalid, but we found a suitable end marker. The data format seems incompatible though, so the MasterTriggerEvents is probably rubbish!");
620          mt.event_status = EventStatus::IncompatibleData; 
621          *pos = tail_pos + 2;
622        },
623        Err(err) => {
624          error!("Tail invalid, we assume the data format is incompatible, however, we could not do anything about it! {err}");
625        }
626      }
627    }
628    Ok(mt)
629  }
630}
631
632impl Default for MasterTriggerEvent {
633  fn default() -> Self {
634    Self::new()
635  }
636}
637
638impl fmt::Display for MasterTriggerEvent {
639  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
640    let mut repr = String::from("<MasterTriggerEvent");
641    repr += &(format!("\n  EventStatus     : {}", self.event_status));
642    repr += &(format!("\n  EventID         : {}", self.event_id));
643    repr += "\n  ** trigger sources **";
644    for k in self.get_trigger_sources() {
645      repr += &(format!("\n   {}", k));
646    }
647    if self.get_global_trigger_soures().len() > 0 {
648      repr += "\n -- global";
649      for k in self.get_global_trigger_soures() {
650        repr += &(format!("\n  {}", k));
651      }
652    }
653    repr += "\n  ** ** timestamps ** **";
654    repr += &(format!("\n    timestamp     : {}", self.timestamp));
655    repr += &(format!("\n    tiu_timestamp : {}", self.tiu_timestamp));
656    repr += &(format!("\n    gps_timestamp : {}", self.tiu_gps32));
657    repr += &(format!("\n    absolute 48bit: {}", self.get_timestamp_abs48()));
658    repr += "\n  -- -- --";
659    repr += &(format!("\n  crc             : {}", self.crc));
660    repr += &(format!("\n  ** ** TRIGGER HITS (DSI/J/CH) [{} LTBS] ** **", self.dsi_j_mask.count_ones()));
661    for k in self.get_trigger_hits() {
662      repr += &(format!("\n  => {}/{}/({},{}) ({}) ", k.0, k.1, k.2.0, k.2.1, k.3));
663    }
664    repr += "\n  ** ** MTB LINK IDs ** **";
665    let mut mtblink_str = String::from("\n  => ");
666    for k in self.get_rb_link_ids() {
667      mtblink_str += &(format!("{} ", k))
668    }
669    repr += &mtblink_str;
670    repr += &(format!("\n  == Trigger hits {}, expected RBEvents {}",
671            self.get_trigger_hits().len(),
672            self.get_rb_link_ids().len()));
673    repr += ">";
674    write!(f,"{}", repr)
675  }
676}
677
678impl From<&TofEventSummary> for MasterTriggerEvent {
679  fn from(tes: &TofEventSummary) -> Self {
680    let mut mte        = MasterTriggerEvent::new();
681    mte.event_status   = tes.status;
682    mte.event_id       = tes.event_id;
683    mte.trigger_source = tes.trigger_sources;
684    mte.tiu_gps32      = tes.timestamp32;
685    mte.tiu_gps16      = tes.timestamp16;
686    mte.dsi_j_mask     = tes.dsi_j_mask;
687    mte.channel_mask   = tes.channel_mask.clone(); 
688    mte.mtb_link_mask  = tes.mtb_link_mask;
689    mte
690  }
691}
692
693#[cfg(feature="random")]
694impl FromRandom for MasterTriggerEvent {
695
696  fn from_random() -> Self {
697    let mut event        = Self::new();
698    let mut rng          = rand::thread_rng();
699    // FIXME - P had figured out how to this, copy his approach
700    //event.event_status   = rng.gen::<u8><();
701    event.event_id       = rng.gen::<u32>();
702    event.timestamp      = rng.gen::<u32>();
703    event.tiu_timestamp  = rng.gen::<u32>();
704    event.tiu_gps32      = rng.gen::<u32>();
705    event.tiu_gps16      = rng.gen::<u16>();
706    event.crc            = rng.gen::<u32>();
707    event.trigger_source = rng.gen::<u16>();
708    event.dsi_j_mask     = rng.gen::<u32>();
709    let n_channel_masks  = rng.gen::<u8>();
710    for _ in 0..n_channel_masks {
711      event.channel_mask.push(rng.gen::<u16>());
712    }
713    event.mtb_link_mask  = rng.gen::<u64>();
714    event
715  }
716}
717
718#[test]
719#[cfg(feature = "random")]
720fn test_trigger_type() {
721  for _ in 0..100 {
722    let ttype = TriggerType::from_random();
723    let ttype_u8 = ttype.to_u8();
724    let u8_ttype = TriggerType::from(ttype_u8);
725    assert_eq!(ttype, u8_ttype);
726  }
727}
728
729#[cfg(all(test,feature = "random"))]
730mod test_mastertriggerevent {
731  use crate::serialization::Serialization;
732  use crate::FromRandom;
733  use crate::events::MasterTriggerEvent;
734  
735  #[test]
736  fn serialization_mastertriggerevent() {
737    let data = MasterTriggerEvent::from_random();
738    let test = MasterTriggerEvent::from_bytestream(&data.to_bytestream(), &mut 0).unwrap();
739    assert_eq!(data, test);
740  }
741}
742