gondola_core/
events.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4pub mod tof_hit;
5pub use tof_hit::TofHit;
6
7pub mod rb_waveform;
8pub use rb_waveform::RBWaveform;
9
10pub mod rb_event_header;
11pub use rb_event_header::RBEventHeader;
12
13pub mod tof_event;
14pub use tof_event::TofEvent;
15
16pub mod rb_event;
17pub use rb_event::{
18  RBEvent,
19  unpack_traces
20};
21
22pub mod tracker_hit;
23pub use tracker_hit::TrackerHit;
24
25pub mod telemetry_event;
26pub use telemetry_event::TelemetryEvent;
27
28use std::fmt;
29
30use strum_macros::{
31  AsRefStr,
32  FromRepr,
33  EnumIter
34};
35// needed for enum macro
36// FIXME 
37#[cfg(feature="random")]
38use strum::IntoEnumIterator;
39use crate::expand_and_test_enum;
40
41#[cfg(feature="pybindings")]
42use pyo3::prelude::*;
43
44#[cfg(feature="random")]
45use crate::random::FromRandom;
46#[cfg(feature="random")]
47use rand::Rng;
48
49/// mask to decode LTB hit masks
50pub const LTB_CH0 : u16 = 0x3   ;
51/// mask to decode LTB hit masks
52pub const LTB_CH1 : u16 = 0xc   ;
53/// mask to decode LTB hit masks
54pub const LTB_CH2 : u16 = 0x30  ; 
55/// mask to decode LTB hit masks
56pub const LTB_CH3 : u16 = 0xc0  ;
57/// mask to decode LTB hit masks
58pub const LTB_CH4 : u16 = 0x300 ;
59/// mask to decode LTB hit masks
60pub const LTB_CH5 : u16 = 0xc00 ;
61/// mask to decode LTB hit masks
62pub const LTB_CH6 : u16 = 0x3000;
63/// mask to decode LTB hit masks
64pub const LTB_CH7 : u16 = 0xc000;
65/// mask to decode LTB channels from bitmask
66pub const LTB_CHANNELS : [u16;8] = [
67  LTB_CH0,
68  LTB_CH1,
69  LTB_CH2,
70  LTB_CH3,
71  LTB_CH4,
72  LTB_CH5,
73  LTB_CH6,
74  LTB_CH7
75];
76
77/// An array of the channel numbers as they come in pairs on the LTB
78pub const PHYSICAL_CHANNELS : [(u8, u8); 8] = [(1u8,  2u8), (3u8,4u8), (5u8, 6u8), (7u8, 8u8),
79                                               (9u8, 10u8), (11u8,12u8), (13u8, 14u8), (15u8, 16u8)];
80
81
82/// Calculate an unique identifier for 
83/// tracker strips from the position in 
84/// the tracker stack
85///
86/// # Arguments:
87///   * layer   : tracker layer (0-9)
88///   * row     : row in layer  (0-6)
89///   * module  : module in row (0-6)
90///   * channel : channel in module (0-32) 
91///
92#[cfg_attr(feature="pybindings", pyfunction)]
93pub fn strip_id(layer : u8, row :u8, module : u8, channel : u8) -> u32 {
94  channel as u32 + (module as u32)*100 + (row as u32)*10000 + (layer as u32)*100000
95}
96  
97/// Get absolute timestamp as sent by the GPS and 
98/// as seen by the MTB
99#[cfg_attr(feature="pybindings", pyfunction)]
100pub fn mt_event_get_timestamp_abs48(mtb_timestamp : u32, gps_timestamp : u32, tiu_timestamp : u32) -> u64 {
101  let gps = gps_timestamp as u64;
102  let mut timestamp = mtb_timestamp as u64;
103  if timestamp < tiu_timestamp as u64 {
104    // it has wrapped
105    timestamp += u32::MAX as u64 + 1;
106  }
107  let gps_mult = match 100_000_000u64.checked_mul(gps) {
108  //let gps_mult = match 100_000u64.checked_mul(gps) {
109    Some(result) => result,
110    None => {
111        // Handle overflow case here
112        // Example: log an error, return a default value, etc.
113        0 // Example fallback value
114    }
115  };
116
117  let ts = gps_mult + (timestamp - tiu_timestamp as u64);
118  ts
119}
120
121#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter)]
122#[repr(u8)]
123#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
124pub enum EventQuality {
125  Unknown        =  0u8,
126  Silver         = 10u8,
127  Gold           = 20u8,
128  Diamond        = 30u8,
129  FourLeafClover = 40u8,
130}
131
132expand_and_test_enum!(EventQuality, test_eventquality_repr);
133
134//--------------------------------------------
135
136// Need serde here, so that we can add it to the liftof configs
137#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter, serde::Serialize, serde::Deserialize)]
138#[repr(u8)]
139#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
140pub enum TriggerType {
141  Unknown         = 0u8,
142  /// -> 1-10 "pysics" triggers
143  Any             = 1u8,
144  Track           = 2u8,
145  TrackCentral    = 3u8,
146  Gaps            = 4u8,
147  Gaps633         = 5u8, 
148  Gaps422         = 6u8,
149  Gaps211         = 7u8,
150  TrackUmbCentral = 8u8,
151  Gaps1044        = 9u8,
152  /// -> 20+ "Philip's triggers"
153  /// Any paddle HIT in UMB  + any paddle HIT in CUB
154  UmbCube         = 21u8,
155  /// Any paddle HIT in UMB + any paddle HIT in CUB top
156  UmbCubeZ        = 22u8,
157  /// Any paddle HIT in UMB + any paddle hit in COR + any paddle hit in CUB 
158  UmbCorCube      = 23u8,
159  /// Any paddle HIT in COR + any paddle HIT in CUB SIDES
160  CorCubeSide     = 24u8,
161  /// Any paddle hit in UMB + any three paddles HIT in CUB
162  Umb3Cube        = 25u8,
163  /// > 100 -> Debug triggers
164  Poisson         = 100u8,
165  Forced          = 101u8,
166  FixedRate       = 102u8,
167  /// > 200 -> These triggers can not be set, they are merely
168  /// the result of what we read out from the trigger mask of 
169  /// the ltb
170  ConfigurableTrigger = 200u8,
171}
172
173impl TriggerType {
174
175  /// In the serialized data, trigger sources are represented by 2bytes. 
176  /// This will regenerate a vector of trigger sources from these bytes
177  pub fn transcode_trigger_sources(trigger_sources : u16) -> Vec<Self> {
178    let mut t_types    = Vec::<Self>::new();
179    let gaps_trigger   = trigger_sources >> 5 & 0x1 == 1;
180    if gaps_trigger {
181      t_types.push(TriggerType::Gaps);
182    }
183    let any_trigger    = trigger_sources >> 6 & 0x1 == 1;
184    if any_trigger {
185      t_types.push(TriggerType::Any);
186    }
187    let forced_trigger = trigger_sources >> 7 & 0x1 == 1;
188    if forced_trigger {
189      t_types.push(TriggerType::Forced);
190    }
191    let track_trigger  = trigger_sources >> 8 & 0x1 == 1;
192    if track_trigger {
193      t_types.push(TriggerType::Track);
194    }
195    let central_track_trigger
196                       = trigger_sources >> 9 & 0x1 == 1;
197    if central_track_trigger {
198      t_types.push(TriggerType::TrackCentral);
199    }
200    t_types
201  }
202 
203  pub fn to_u8(&self) -> u8 {
204    match self {
205      TriggerType::Unknown => {
206        return 0;
207      }
208      TriggerType::Poisson => {
209        return 100;
210      }
211      TriggerType::Forced => {
212        return 101;
213      }
214      TriggerType::FixedRate => {
215        return 102;
216      }
217      TriggerType::Any => {
218        return 1;
219      }
220      TriggerType::Track => {
221        return 2;
222      }
223      TriggerType::TrackCentral => {
224        return 3;
225      }
226      TriggerType::Gaps => {
227        return 4;
228      }
229      TriggerType::Gaps633 => {
230        return 5;
231      }
232      TriggerType::Gaps422 => {
233        return 6;
234      }
235      TriggerType::Gaps211 => {
236        return 7;
237      }
238      TriggerType::TrackUmbCentral => {
239        return 8;
240      }
241      TriggerType::Gaps1044 => {
242        return 9;
243      }
244      TriggerType::UmbCube => {
245        return 21;
246      }
247      TriggerType::UmbCubeZ => {
248        return 22; 
249      }
250      TriggerType::UmbCorCube => {
251        return 23;
252      }
253      TriggerType::CorCubeSide => {
254        return 24;
255      }
256      TriggerType::Umb3Cube => {
257        return 25;
258      }
259      TriggerType::ConfigurableTrigger => {
260        return 200;  
261      }
262    }
263  }
264}
265
266#[cfg(feature="pybindings")]
267#[pymethods]
268impl TriggerType {
269  #[staticmethod]
270  #[pyo3(name="transcode_trigger_sources")]
271  fn transcode_trigger_sources_py(trigger_sources : u16) -> Vec<Self> {
272    TriggerType::transcode_trigger_sources(trigger_sources)
273  }
274}
275
276expand_and_test_enum!(TriggerType, test_triggertype_repr);
277
278//--------------------------------------------
279
280/// LTB Thresholds as passed on by the MTB
281/// [See also](https://gaps1.astro.ucla.edu/wiki/gaps/images/gaps/5/52/LTB_Data_Format.pdf)
282#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter)]
283#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
284#[repr(u8)]
285pub enum LTBThreshold {
286  NoHit = 0u8,
287  /// First threshold, 40mV, about 0.75 minI
288  Hit   = 1u8,
289  /// Second threshold, 32mV (? error in doc ?, about 2.5 minI
290  Beta  = 2u8,
291  /// Third threshold, 375mV about 30 minI
292  Veto  = 3u8,
293  /// Use u8::MAX for Unknown, since 0 is pre-determined for 
294  /// "NoHit, 
295  Unknown = 255u8
296}
297
298expand_and_test_enum!(LTBThreshold, test_ltbthreshold_repr);
299
300//--------------------------------------------
301
302#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, FromRepr, AsRefStr, EnumIter)]
303#[repr(u8)]
304#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
305pub enum EventStatus {
306  Unknown                = 0u8,
307  CRC32Wrong             = 10u8,
308  TailWrong              = 11u8,
309  ChannelIDWrong         = 12u8,
310  /// one of the channels cells CellSyncError bits 
311  /// has been set (RB)
312  CellSyncErrors         = 13u8,
313  /// one of the channels ChannelSyncError bits 
314  /// has been set (RB)
315  ChnSyncErrors          = 14u8,
316  /// Both of the bits (at least one for the cell sync errors)
317  /// have been set
318  CellAndChnSyncErrors   = 15u8,
319  /// If any of the RBEvents have Sync erros, we flag the tof 
320  /// event summary to indicate there were issues
321  AnyDataMangling        = 16u8,
322  /// RB is missing, but it is expected that is missing
323  /// when we compare the trigger information with the 
324  /// list of known dead rbs
325  KnownDeadRB            = 17u8,
326  IncompleteReadout      = 21u8,
327  /// This can be used if there is a version
328  /// missmatch and we have to hack something
329  IncompatibleData       = 22u8,
330  /// The TofEvent timed out while waiting for more Readoutboards
331  EventTimeOut           = 23u8,
332  /// A RB misses Ch9 data
333  NoChannel9             = 24u8,
334  /// A RBReceives a strange event id 
335  RBEventWacky           = 25u8,
336  GoodNoCRCOrErrBitCheck = 39u8,
337  /// The event status is good, but we did not 
338  /// perform any CRC32 check
339  GoodNoCRCCheck         = 40u8,
340  /// The event is good, but we did not perform
341  /// error checks
342  GoodNoErrBitCheck      = 41u8,
343  Perfect                = 42u8
344}
345
346// in case we have pybindings for this type, 
347// expand it so that it can be used as keys
348// in dictionaries
349#[cfg(feature = "pybindings")]
350#[pymethods]
351impl EventStatus {
352
353  #[getter]
354  fn __hash__(&self) -> usize {
355    (*self as u8) as usize
356  } 
357}
358
359
360expand_and_test_enum!(EventStatus, test_eventstatus_repr);
361
362//--------------------------------------------
363
364/// A generic data type
365///
366/// Describe the purpose of the data. This
367/// is the semantics behind it.
368#[derive(Debug, Copy, Clone, PartialEq,FromRepr, AsRefStr, EnumIter, serde::Deserialize, serde::Serialize)]
369#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
370#[repr(u8)]
371pub enum DataType {
372  Unknown            = 0u8,
373  VoltageCalibration = 10u8,
374  TimingCalibration  = 20u8,
375  Noi                = 30u8,
376  Physics            = 40u8,
377  RBTriggerPeriodic  = 50u8,
378  RBTriggerPoisson   = 60u8,
379  MTBTriggerPoisson  = 70u8,
380  // future extension for different trigger settings!
381}
382
383expand_and_test_enum!(DataType, test_datatype_repr);
384
385//--------------------------------------------
386