Skip to main content

gondola_core/
monitoring.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4pub mod pa_moni_data;
5pub use pa_moni_data::{
6  PAMoniData,
7  PAMoniDataSeries
8};
9pub mod pb_moni_data;
10pub use pb_moni_data::{
11  PBMoniData,
12  PBMoniDataSeries
13};
14pub mod mtb_moni_data;
15pub use mtb_moni_data::{
16  MtbMoniData,
17  MtbMoniDataSeries
18};
19pub mod ltb_moni_data;
20pub use ltb_moni_data::{
21  LTBMoniData,
22  LTBMoniDataSeries
23};
24pub mod rb_moni_data;
25pub use rb_moni_data::{
26  RBMoniData,
27  RBMoniDataSeries
28};
29pub mod cpu_moni_data;
30pub use cpu_moni_data::{
31  CPUMoniData,
32  CPUMoniDataSeries
33};
34
35pub mod heartbeats;
36pub use heartbeats::{
37  DataSinkHB,
38  DataSinkHBSeries,
39  MasterTriggerHB,
40  MasterTriggerHBSeries,
41  EventBuilderHB,
42  EventBuilderHBSeries,
43};
44
45pub mod run_statistics;
46pub use run_statistics::RunStatistics;
47
48pub mod sip_position;
49pub use sip_position::{
50  SipPosMoniData,
51  SipPosMoniDataSeries
52};
53
54pub mod sip_pressure;
55pub use sip_pressure::{
56  SipPresMoniData,
57  SipPresMoniDataSeries,
58};
59
60pub mod sip_time;
61pub use sip_time::{
62  SipTimeMoniData,
63  SipTimeMoniDataSeries
64};
65
66pub mod gcu_ev_stats;
67pub use gcu_ev_stats::{
68  GcuEvBldStatsMoniData,
69  GcuEvBldStatsMoniDataSeries
70};
71
72pub mod tracker_gps;
73pub use tracker_gps::{
74  TrackerGpsMoniData,
75  TrackerGpsMoniDataSeries
76}; 
77
78pub mod cooling; 
79pub use cooling::{
80  CoolingMoniData,
81  CoolingMoniDataSeries
82};
83
84pub mod wastie;
85pub use wastie::*;
86
87use std::collections::VecDeque;
88use std::collections::HashMap;
89
90#[cfg(feature="pybindings")]
91use crate::prelude::*;
92
93#[cfg(feature="pybindings")]
94use polars::frame::column::Column;
95#[cfg(feature="pybindings")]
96use polars::prelude::NamedFrom; 
97
98/// Monitoring data shall share the same kind 
99/// of interface. 
100pub trait MoniData {
101  /// Monitoring data is always tied to a specific
102  /// board. This might not be its own board, but 
103  /// maybe the RB the data was gathered from
104  /// This is an unique identifier for the 
105  /// monitoring data
106  fn get_board_id(&self) -> u8;
107  
108  /// Access the (data) members by name 
109  fn get(&self, varname : &str) -> Option<f32>;
110
111  /// A list of the variables in this MoniData
112  fn keys() -> Vec<&'static str>;
113
114  fn get_timestamp(&self) -> u64 {
115    0
116  }
117
118  fn set_timestamp(&mut self, _ts : u64) {
119  }
120  ///// access the internal timestamps as obtained from 
121  ///// MoniDat 
122  //fn get_timestamps_mut(&mut self) -> &Vec<u64> {
123  //}   
124}
125
126/// A MoniSeries is a collection of (primarily) monitoring
127/// data, which comes from multiple senders.
128/// E.g. a MoniSeries could hold RBMoniData from all 
129/// 40 ReadoutBoards.
130pub trait MoniSeries<T>
131  where T : Copy + MoniData {
132
133  fn get_first_ts(&self) -> u64;
134
135  fn get_data(&self) -> &HashMap<u8,VecDeque<T>>;
136
137  fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<T>>;
138 
139  fn get_max_size(&self) -> usize;
140
141  fn set_max_size(&mut self, size : usize);
142
143  fn get_timestamps(&self) -> &Vec<u64>;
144
145  fn add_timestamp(&mut self, ts : u64);
146
147  /// A HashMap of -> rbid, Vec\<var\> 
148  fn get_var(&self, varname : &str) -> HashMap<u8, Vec<f32>> {
149    let mut values = HashMap::<u8, Vec<f32>>::new();
150    for k in self.get_data().keys() {
151      match self.get_var_for_board(varname, k) {
152        None => (),
153        Some(vals) => {
154          values.insert(*k, vals);
155        }
156      }
157      //values.insert(*k, Vec::<f32>::new());
158      //match self.get_data().get(k) {
159      //  None => (),
160      //  Some(vec_moni) => {
161      //    for moni in vec_moni {
162      //      match moni.get(varname) {
163      //        None => (),
164      //        Some(val) => {
165      //          values.get_mut(k).unwrap().push(val);
166      //        }
167      //      }
168      //    }
169      //  }
170      //}
171    }
172    values
173  }
174
175  /// Get a certain variable, but only for a single board
176  fn get_var_for_board(&self, varname : &str, rb_id : &u8) -> Option<Vec<f32>> {
177    let mut values = Vec::<f32>::new();
178    match self.get_data().get(&rb_id) {
179      None => (),
180      Some(vec_moni) => {
181        for moni in vec_moni {
182          match moni.get(varname) {
183            None => {
184              return None;
185            },
186            Some(val) => {
187              values.push(val);
188            }
189          }
190        }
191      }
192    }
193    // FIXME This needs to be returning a reference,
194    // not cloning
195    Some(values)
196  }
197
198  #[cfg(feature = "pybindings")]
199  fn get_dataframe(&self) -> PolarsResult<DataFrame> {
200    let mut series = Vec::<Column>::new();
201    for k in Self::keys() {
202      match self.get_series(k) {
203        None => {
204          error!("Unable to get series for {}", k);
205        }
206        Some(ser) => {
207          //println!("{}", ser);
208          series.push(ser.into());
209        }
210      }
211    }
212    //if self.get_timestamps().len() > 0 {
213    //  let timestamps  = Series::new("timestamps".into(), self.get_timestamps());
214    //  series.push(timestamps.into());
215    //}
216    // each column is now the specific variable but in terms for 
217    // all rbs
218    let df = DataFrame::new(series)?;
219    Ok(df)
220  }
221
222  #[cfg(feature = "pybindings")]
223  /// Get the variable for all boards. This keeps the order of the 
224  /// underlying VecDeque. Values of all boards intermixed.
225  /// To get a more useful version, use the Dataframe instead.
226  ///
227  /// # Arguments
228  ///
229  /// * varname : The name of the attribute of the underlying
230  ///             moni structure
231  fn get_series(&self, varname : &str) -> Option<Series> {
232    let mut data = Vec::<f32>::with_capacity(self.get_data().len());
233    let sorted_keys: Vec<u8> = self.get_data().keys().cloned().collect();
234    for board_id in sorted_keys.iter() {
235      let dqe = self.get_data().get(board_id).unwrap(); //uwrap is fine, bc we checked earlier
236      for moni in dqe {
237        match moni.get(varname) {
238          None => {
239            error!("This type of MoniData does not have a key called {}", varname);
240            return None;
241          }
242          Some(var) => {
243            data.push(var);
244          }
245        }
246      }
247    }
248    let series = Series::new(varname.into(), data);
249    Some(series)
250  }
251
252  /// A list of the variables in this MoniSeries
253  fn keys() -> Vec<&'static str> {
254    T::keys()
255  }
256
257  /// A list of boards in this series
258  fn get_board_ids(&self) -> Vec<u8> {
259    self.get_data().keys().cloned().collect()
260  }
261
262  /// Add another instance of the data container to the series
263  fn add(&mut self, data : T) {
264    let board_id = data.get_board_id();
265    if !self.get_data().contains_key(&board_id) {
266      self.get_data_mut().insert(board_id, VecDeque::<T>::new());
267    } 
268    self.get_data_mut().get_mut(&board_id).unwrap().push_back(data);
269    if self.get_data_mut().get_mut(&board_id).unwrap().len() > self.get_max_size() {
270      error!("The queue is too large, returning the first element! If you need a larger series size, set the max_size field");
271      self.get_data_mut().get_mut(&board_id).unwrap().pop_front();
272    }
273  }
274  
275  fn get_last_moni(&self, board_id : u8) -> Option<T> {
276    let size = self.get_data().get(&board_id)?.len();
277    Some(self.get_data().get(&board_id).unwrap()[size - 1])
278  }
279}
280
281//--------------------------------------------------
282
283#[macro_export]
284macro_rules! moniseries_general {
285  ($name : ident, $class:ty) => {
286     use std::collections::VecDeque;
287     use std::collections::HashMap;
288
289     use crate::monitoring::MoniSeries;
290
291     #[cfg_attr(feature="pybindings",pyclass)]
292     #[derive(Debug, Clone, PartialEq)]
293     pub struct $name {
294       data        : HashMap<u8, VecDeque<$class>>,
295       max_size    : usize,
296       timestamps  : Vec<u64>,
297     }
298     
299     impl $name {
300       pub fn new() -> Self {
301        Self {
302           data       : HashMap::<u8, VecDeque<$class>>::new(),
303           max_size   : 10000,
304           timestamps : Vec::<u64>::new()
305         }
306       }
307     } 
308     
309     impl Default for $name {
310       fn default() -> Self {
311         Self::new()
312       }
313     }
314     
315     impl fmt::Display for $name {
316       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
317         write!(f, "<{} : {} boards>", stringify!($name), self.data.len())
318       }
319     }
320     
321     impl MoniSeries<$class> for $name {
322   
323       fn get_first_ts(&self) -> u64 {
324         if self.timestamps.len() == 0 {
325           return 0;
326         } else {
327           self.timestamps[0]
328         }
329       }
330
331       fn get_data(&self) -> &HashMap<u8,VecDeque<$class>> {
332         return &self.data;
333       }
334     
335       fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<$class>> {
336         return &mut self.data;
337       }
338      
339       fn get_max_size(&self) -> usize {
340         return self.max_size;
341       }
342       
343       fn set_max_size(&mut self, size : usize) {
344         self.max_size = size;
345       }
346  
347       fn add_timestamp(&mut self, ts : u64) {
348         self.timestamps.push(ts);
349       }
350
351       fn get_timestamps(&self) -> &Vec<u64> {
352         //if self.timestamps.len() == 0 {
353         //  let mut timestamps = Vec::<u64>::new();
354         //  for k in self.
355         //} 
356         return &self.timestamps;
357       }
358     }
359  
360     #[cfg(feature="pybindings")]
361     #[pymethods]
362     impl $name {
363       #[new]
364       fn new_py() -> Self {
365         Self::new() 
366       }
367   
368       /// The maximum size of the series. If more data 
369       /// are added, data from the front will be removed 
370       #[getter]
371       #[pyo3(name="max_size")]
372       fn get_max_size_py(&self) -> usize {
373         self.get_max_size()
374       }
375
376       #[setter]
377       #[pyo3(name="max_size")] 
378       fn set_max_size_py(&mut self, size : usize) {
379         self.set_max_size(size);
380       }
381       
382
383       #[getter]
384       #[pyo3(name="first_ts")]
385       pub fn get_first_ts_py(&self) -> u64 {
386         self.get_first_ts()
387       }
388
389       /// If monitoring is retrieved from telemetry, we 
390       /// save the gcu timestamp of the packet, wich 
391       /// herein can be accessed.
392       #[getter] 
393       #[pyo3(name="timestamps")] 
394       fn get_timestamps_py(&self) -> Vec<u64> {
395         warn!("This returns a full copy and is a performance bottleneck!");
396         return self.timestamps.clone();
397       }
398
399       #[pyo3(name="get_var_for_board")]
400       fn get_var_for_board_py(&self, varname : &str, rb_id : u8) -> Option<Vec<f32>> {
401         self.get_var_for_board(varname, &rb_id)
402       }
403
404       /// Reduces the MoniSeries to a single polars data frame
405       /// The structure itself will not be changed
406       #[pyo3(name="get_dataframe")]
407       fn get_dataframe_py(&self) -> PyResult<PyDataFrame> {
408         match self.get_dataframe() {
409           Ok(df) => {
410             let pydf = PyDataFrame(df);
411             return Ok(pydf);
412           },
413           Err(err) => {
414             return Err(PyValueError::new_err(err.to_string()));
415           }
416         }
417       } 
418     }
419     #[cfg(feature="pybindings")]
420     pythonize_display!($name);
421  }
422}
423
424/// Implements the moniseries trait for a MoniData 
425/// type of class
426#[macro_export]
427macro_rules! moniseries {
428  ($name : ident, $class:ty) => {
429    
430    moniseries_general!($name, $class);
431    //use std::collections::VecDeque;
432    //use std::collections::HashMap;
433
434    //use crate::monitoring::MoniSeries;
435
436    //#[cfg_attr(feature="pybindings",pyclass)]
437    //#[derive(Debug, Clone, PartialEq)]
438    //pub struct $name {
439    //  data        : HashMap<u8, VecDeque<$class>>,
440    //  max_size    : usize,
441    //  timestamps  : Vec<u64>,
442    //}
443    //
444    //impl $name {
445    //  pub fn new() -> Self {
446    //    Self {
447    //      data       : HashMap::<u8, VecDeque<$class>>::new(),
448    //      max_size   : 10000,
449    //      timestamps : Vec::<u64>::new()
450    //    }
451    //  }
452    //} 
453    //
454    //impl Default for $name {
455    //  fn default() -> Self {
456    //    Self::new()
457    //  }
458    //}
459    //
460    //impl fmt::Display for $name {
461    //  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
462    //    write!(f, "<{} : {} boards>", stringify!($name), self.data.len())
463    //  }
464    //}
465    //
466    //impl MoniSeries<$class> for $name {
467   
468    //  fn get_first_ts(&self) -> u64 {
469    //    if self.timestamps.len() == 0 {
470    //      return 0;
471    //    } else {
472    //      self.timestamps[0]
473    //    }
474    //  }
475
476    //  fn get_data(&self) -> &HashMap<u8,VecDeque<$class>> {
477    //    return &self.data;
478    //  }
479    //
480    //  fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<$class>> {
481    //    return &mut self.data;
482    //  }
483    // 
484    //  fn get_max_size(&self) -> usize {
485    //    return self.max_size;
486    //  }
487    //  
488    //  fn set_max_size(&mut self, size : usize) {
489    //    self.max_size = size;
490    //  }
491  
492    //  fn add_timestamp(&mut self, ts : u64) {
493    //    self.timestamps.push(ts);
494    //  }
495
496    //  fn get_timestamps(&self) -> &Vec<u64> {
497    //    //if self.timestamps.len() == 0 {
498    //    //  let mut timestamps = Vec::<u64>::new();
499    //    //  for k in self.
500    //    //} 
501    //    return &self.timestamps;
502    //  }
503    //}
504  
505    //#[cfg(feature="pybindings")]
506    //#[pymethods]
507    //impl $name {
508    //  #[new]
509    //  fn new_py() -> Self {
510    //    Self::new() 
511    //  }
512   
513    //  /// The maximum size of the series. If more data 
514    //  /// are added, data from the front will be removed 
515    //  #[getter]
516    //  #[pyo3(name="max_size")]
517    //  fn get_max_size_py(&self) -> usize {
518    //    self.get_max_size()
519    //  }
520
521    //  #[setter]
522    //  #[pyo3(name="max_size")] 
523    //  fn set_max_size_py(&mut self, size : usize) {
524    //    self.set_max_size(size);
525    //  }
526    //  
527
528    //  #[getter]
529    //  #[pyo3(name="get_first_ts")]
530    //  fn get_first_ts_py(&self) -> u64 {
531    //    self.get_first_ts()
532    //  }
533
534    //  /// If monitoring is retrieved from telemetry, we 
535    //  /// save the gcu timestamp of the packet, wich 
536    //  /// herein can be accessed.
537    //  #[getter] 
538    //  #[pyo3(name="timestamps")] 
539    //  fn get_timestamps_py(&self) -> Vec<u64> {
540    //    warn!("This returns a full copy and is a performance bottleneck!");
541    //    return self.timestamps.clone();
542    //  }
543
544    #[cfg(feature="pybindings")]
545    #[pymethods]
546    impl $name {
547
548      /// Add an additional (Caraspace) file to the series 
549      ///
550      /// # Arguments:
551      ///   * filename    : The name of the (caraspace) file to add
552      ///   * from_object : Since this adds caraspace files, it is possible 
553      ///                   to choose from where to get the information.
554      ///                   Either the telemetry packet, or the tofpacket, if 
555      ///                   either is present in the frame. When 
556      ///                   CRFrameObjectType = Unknown, it will figure it out 
557      ///                   automatically, preferring the telemetry since it has
558      ///                   the gcu timestamp
559      #[pyo3(signature = (filename, from_object = CRFrameObjectType::TelemetryPacket))]
560      fn add_crfile(&mut self, filename : String, from_object : CRFrameObjectType) {
561        let reader = CRReader::new(filename).expect("Unable to open file!");
562        // now we have a problem - from which frame should we get it?
563        // if we get it from the dedicated TOF stream it will be much 
564        // faster (if that is available) since it will be it's own 
565        // presence in the frame
566        //let address = &source.clone();
567        //let mut try_from_telly = false;
568        let tp_source     = String::from("TofPacketType.") + stringify!($class);
569        let tp_source_alt = String::from("PacketType.") + stringify!($class);
570        let tel_source    = "TelemetryPacketType.AnyTofHK";
571        for frame in reader {
572          match from_object { 
573            CRFrameObjectType::TofPacket =>  {
574              if frame.has(&tp_source) || frame.has(&tp_source_alt) {
575                if frame.has(&tp_source) {
576                  let moni_res = frame.get::<TofPacket>(&tp_source).unwrap().unpack::<$class>();
577                  match moni_res {
578                    Err(err) => {
579                      println!("Error unpacking! {err}");
580                    }
581                    Ok(moni) => {
582                      self.add(moni);
583                    }
584                  }
585                }
586                if frame.has(&tp_source_alt) {
587                  let moni_res = frame.get::<TofPacket>(&tp_source_alt).unwrap().unpack::<$class>();
588                  match moni_res { 
589                    Err(err) => {
590                      println!("Error unpacking! {err}");
591                    }
592                    Ok(moni) => {
593                      self.add(moni);
594                    }
595                  }
596                }
597              } 
598            }
599            CRFrameObjectType::TelemetryPacket | CRFrameObjectType::Unknown => {
600              if frame.has(tel_source) {
601                let hk_res = frame.get::<TelemetryPacket>(tel_source);
602                match hk_res {
603                  Err(err) => {
604                    println!("Error unpacking! {err}");
605                  }
606                  Ok(hk) => {
607                    let mut pos = 0;
608                    // subtract the 2020/1/1 midnight from the gcutime to make 
609                    // it f32
610                    let gcutime = hk.header.get_gcutime() as u64;
611                    match TofPacket::from_bytestream(&hk.payload, &mut pos) {
612                      Err(err) => {
613                        println!("Error unpackin! {err}");
614                      }
615                      Ok(tp) => {
616                        if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
617                          match tp.unpack::<$class>() {
618                            Err(err) => {
619                              println!("Error unpacking! {err}");
620                            }
621                            Ok(mut moni) => {
622                              self.add_timestamp(gcutime);
623                              moni.set_timestamp(gcutime - self.get_first_ts()); 
624                              self.add(moni);
625                              //self.timestamps.push(gcutime);
626                            }
627                          }
628                        }
629                      }
630                    } 
631                  }
632                }
633              }
634            }
635            _ => () // do nothing for other types
636          }
637        }
638      }
639      
640      /// Add an additional (Telemetry) file to the series 
641      ///
642      /// # Arguments:
643      ///   * filename    : The name of the (telemetry) file to add
644      fn add_telemetryfile(&mut self, filename : String) {
645        let reader = TelemetryPacketReader::new(filename, true, None, None, 0, 0);
646        for pack in reader {
647          if pack.header.packet_type == TelemetryPacketType::AnyTofHK {
648            let mut pos = 0;
649            // subtract the 2020/1/1 midnight from the gcutime to make 
650            // it f32
651            let gcutime = pack.header.get_gcutime() as u64;
652            match TofPacket::from_bytestream(&pack.payload, &mut pos) {
653              Err(err) => {
654                println!("Error unpackin! {err}");
655              }
656              Ok(tp) => {
657                if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
658                  match tp.unpack::<$class>() {
659                    Err(err) => {
660                      println!("Error unpacking! {err}");
661                    }
662                    Ok(mut moni) => {
663                      self.add_timestamp(gcutime);
664                      moni.set_timestamp(gcutime - self.get_first_ts()); 
665                      self.add(moni);
666                      //self.timestamps.push(gcutime);
667                    }
668                  }
669                }
670              }
671            } 
672          }
673        }
674      }
675      
676      /// Add an additional (Tof) file to the series 
677      ///
678      /// # Arguments:
679      ///   * filename    : The name of the (caraspace) file to add
680      ///   * from_object : Since this adds caraspace files, it is possible 
681      ///                   to choose from where to get the information.
682      ///                   Either the telemetry packet, or the tofpacket, if 
683      ///                   either is present in the frame. When 
684      ///                   CRFrameObjectType = Unknown, it will figure it out 
685      ///                   automatically, preferring the telemetry since it has
686      ///                   the gcu timestamp
687      #[pyo3(signature = (filename))]
688      fn add_toffile(&mut self, filename : String) {
689        let reader = TofPacketReader::new(&filename);
690        for tp in reader { 
691          if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
692            match tp.unpack::<$class>() {
693              Err(err) => {
694                println!("Error unpacking! {err}");
695              }
696              Ok(mut moni) => {
697                self.add_timestamp(moni.get_timestamp());
698                moni.set_timestamp(moni.get_timestamp()); 
699                self.add(moni);
700                //self.timestamps.push(gcutime);
701              }
702            }
703          }
704        }
705      }
706
707      /// Generate a polars dataframe with monitoring data from the 
708      /// given TOF file.
709      /// This will load ONLY data of the specific type of the 
710      /// MoniSeries itself
711      ///
712      /// # Arguments:
713      ///   * filename : A single .tof.gaps file with monitoring 
714      ///                information 
715      #[staticmethod]
716      fn from_tof_file(filename : String) -> PyResult<PyDataFrame> {
717        let mut reader = TofPacketReader::new(&filename);
718        let mut series = Self::new();
719        // it would be nice to set the filter here, but I 
720        // don't know how that can be done in the macro
721        reader.filter  = <$class>::TOF_PACKET_TYPE;
722        for tp in reader {
723          if let Ok(moni) =  tp.unpack::<$class>() {
724            series.add(moni);
725          }
726          // other packets will get thrown away 
727        }
728        match series.get_dataframe() {
729          Ok(df) => {
730            let pydf = PyDataFrame(df);
731            return Ok(pydf);
732          },
733          Err(err) => {
734            return Err(PyValueError::new_err(err.to_string()));
735          }
736        }
737      }
738      
739      //#[pyo3(name="get_var_for_board")]
740      //fn get_var_for_board_py(&self, varname : &str, rb_id : u8) -> Option<Vec<f32>> {
741      //  self.get_var_for_board(varname, &rb_id)
742      //}
743
744      ///// Reduces the MoniSeries to a single polars data frame
745      ///// The structure itself will not be changed
746      //#[pyo3(name="get_dataframe")]
747      //fn get_dataframe_py(&self) -> PyResult<PyDataFrame> {
748      //  match self.get_dataframe() {
749      //    Ok(df) => {
750      //      let pydf = PyDataFrame(df);
751      //      return Ok(pydf);
752      //    },
753      //    Err(err) => {
754      //      return Err(PyValueError::new_err(err.to_string()));
755      //    }
756      //  }
757      //}
758
759      ////fn get_pl_series_py(&self) -> PyResult<PyS
760      ////fn get_data(&self) -> &HashMap<u8,VecDeque<$class>> {
761      ////  return &self.data;
762      ////}
763    
764      ////fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<$class>> {
765      ////  return &mut self.data;
766      ////}
767     
768      ////fn get_max_size(&self) -> usize {
769      ////  return self.max_size;
770      ////}
771    }
772    
773    //#[cfg(feature="pybindings")]
774    //pythonize_display!($name);
775  }
776}
777
778// ------------------------------------------------
779
780// baiscally the same macro, but for data coming 
781// from telemetry 
782
783/// Implements the moniseries trait for a MoniData 
784/// type of class
785#[macro_export]
786macro_rules! moniseries_telemetry {
787  ($name : ident, $class:ty) => {
788    
789    moniseries_general!($name, $class);
790
791    #[cfg(feature="pybindings")]
792    #[pymethods]
793    impl $name {
794
795      /// Add an additional (Caraspace) file to the series 
796      ///
797      /// # Arguments:
798      ///   * filename    : The name of the (caraspace) file to add
799      ///   * from_object : Since this adds caraspace files, it is possible 
800      ///                   to choose from where to get the information.
801      ///                   Either the telemetry packet, or the tofpacket, if 
802      ///                   either is present in the frame. When 
803      ///                   CRFrameObjectType = Unknown, it will figure it out 
804      ///                   automatically, preferring the telemetry since it has
805      ///                   the gcu timestamp
806      #[pyo3(signature = (filename, from_object = CRFrameObjectType::TelemetryPacket))]
807      fn add_crfile(&mut self, filename : String, from_object : CRFrameObjectType) {
808        let reader = CRReader::new(filename).expect("Unable to open file!");
809        // now we have a problem - from which frame should we get it?
810        // if we get it from the dedicated TOF stream it will be much 
811        // faster (if that is available) since it will be it's own 
812        // presence in the frame
813        //let address = &source.clone();
814        //let mut try_from_telly = false;
815        let tel_source    = "TelemetryPacketType.AnyTofHK";
816        for frame in reader {
817          match from_object { 
818            CRFrameObjectType::TelemetryPacket | CRFrameObjectType::Unknown => {
819              if frame.has(tel_source) {
820                let hk_res = frame.get::<TelemetryPacket>(tel_source);
821                match hk_res {
822                  Err(err) => {
823                    println!("Error unpacking! {err}");
824                  }
825                  Ok(hk) => {
826                    if hk.header.packet_type != <$class>::TEL_PACKET_TYPE  {
827                      // this is not the packet you are looking for 
828                      continue;
829                    }   
830                    let mut pos = 0;
831                    // subtract the 2020/1/1 midnight from the gcutime to make 
832                    // it f32
833                    let gcutime = hk.header.get_gcutime() as u64;
834                    match <$class>::from_bytestream(&hk.payload, &mut pos) {
835                      Err(err) => {
836                        println!("Error unpackin! {err}");
837                      }
838                      Ok(mut moni) => {
839                        self.add_timestamp(gcutime);
840                        moni.set_timestamp(gcutime - self.get_first_ts()); 
841                        self.add(moni);
842                        //self.timestamps.push(gcutime);
843                      }
844                    } 
845                  }
846                }
847              }
848            }
849            _ => ()
850          }
851        }
852      }
853      
854      /// Add an additional (Telemetry) file to the series 
855      ///
856      /// # Arguments:
857      ///   * filename    : The name of the (telemetry) file to add
858      fn add_telemetryfile(&mut self, filename : String) {
859        let reader = TelemetryPacketReader::new(filename, true, None, None, 0, 0);
860        for pack in reader {
861          if pack.header.packet_type == <$class>::TEL_PACKET_TYPE {
862            let mut pos = 0;
863            // subtract the 2020/1/1 midnight from the gcutime to make 
864            // it f32
865            let gcutime = pack.header.get_gcutime() as u64;
866            match <$class>::from_bytestream(&pack.payload, &mut pos) {
867              Err(err) => {
868                println!("Error unpackin! {err}");
869              }
870              Ok(mut moni) => {
871                self.add_timestamp(gcutime);
872                moni.set_timestamp(gcutime - self.get_first_ts()); 
873                self.add(moni);
874              }
875            }
876          }
877        }
878      }
879    }
880  }
881}
882
883