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