Skip to main content

gondola_core/
lib.rs

1//! Dataclasses provides structures to facilitate the work with data drom 
2//! the GAPS experiment. Most noticeably, there are
3//!
4//! * events       - TOF/Tracker data, TOF events on disk, MergedEvents send over telemetry
5//!
6//! * packets      - containers to serialize/deserialize the described structures so that 
7//!                  these can be stored on disk or send over the network
8//!
9//! * calibration  - TOF/Tracker related calibration routines and containers to hold results
10//! 
11//! * io           - read/write packets to/from disk or receive them over the network
12//! 
13//! * random       - random numbers for software tests
14//! 
15//! * tof          - Very specific TOF related code which does not fall under a different 
16//!                  category
17//! 
18//! * tracker      - Very specific TRK related code which does not fall under any other 
19//!                  category
20//!
21//! # features:
22//!
23//! * random              - allow random number generated data classes for 
24//!                         testing
25//!
26//! * database            - access a data base for advanced paddle
27//!                         mapping, readoutboard and ltb information etc.
28//!                         This will introduce a dependency on sqlite and 
29//!                         diesel
30//! * tof-control         - allows to control LTB & Powerboard from the RBs 
31//!                         over i2c. Since the i2c protocoll is not supported 
32//!                         on Mac, code with this feature enable won't build 
33//!                         on Apple systems. 
34//! * advanced-algorithms - allows to use a different algorithm for pulse extraction 
35//! * pybindings          - build the python library "gondola". Most of the entitities 
36//!                         within "gondola-core" have pybindings, e.g. events, hits.
37//! * tof-liftof          - build the code which is required to build liftof, the flight
38//!                         code utiilizing this library 
39// This file is part of gaps-online-software and published 
40// under the GPLv3 license
41
42#[macro_use] extern crate log; 
43
44pub mod prelude;
45#[cfg(feature="random")]
46pub mod random;
47pub mod constants;
48pub mod events;
49pub mod packets;
50pub mod version;
51pub mod io;
52pub mod calibration;
53pub mod errors;
54pub mod tof;
55pub mod tracker;
56pub mod monitoring;
57pub mod stats;
58pub mod physics;
59#[cfg(feature="pybindings")]
60pub mod python;
61#[cfg(feature="database")]
62pub mod database;
63
64// python convention
65pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 
66pub const VERSION_MAJ: &str = env!("CARGO_PKG_VERSION_MAJOR");
67pub const VERSION_MIN: &str = env!("CARGO_PKG_VERSION_MINOR");
68pub const VERSION_PCH: &str = env!("CARGO_PKG_VERSION_PATCH");
69
70#[cfg(feature="pybindings")]
71use crate::errors::*;
72
73/// A simple helper macro adding an as_str function 
74/// as well as the Display method to any enum.
75///
76/// Avoids writing boilerplate
77#[macro_export]
78macro_rules! expand_and_test_enum {
79  ($name:ident, $test_name:ident) => {
80    impl fmt::Display for $name {
81      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        write!(f, "<{}: {}>",stringify!($name), self.as_ref())
83      }
84    }
85
86    impl From<u8> for $name {
87      fn from(value: u8) -> Self {
88        match Self::from_repr(value)  {
89          None => {
90            return Self::Unknown;
91          }
92          Some(variant) => {
93            return variant;
94          }
95        }
96      }
97    }
98
99    #[cfg(feature="random")]
100    impl FromRandom for $name {
101      fn from_random() -> Self {
102        let mut choices = Vec::<Self>::new();
103        for k in Self::iter() {
104          choices.push(k);
105        }
106        let mut rng  = rand::rng();
107        let idx = rng.random_range(0..choices.len());
108        choices[idx]
109      }
110    }
111
112    #[test]
113    fn $test_name() {
114      for _ in 0..100 {
115        let data = $name::from_random();
116        assert_eq!($name::from(data as u8), data);
117      }
118    }
119
120    #[cfg(feature="pybindings")]
121    #[pymethods]
122    impl $name {
123    
124      #[staticmethod]  
125      #[pyo3(name = "from_u8")]
126      fn from_py(byte : u8) -> Self {
127        Self::from(byte) 
128      }
129    }
130  };
131}
132
133//---------------------------------------
134// helpers to init the logging system
135//
136
137use colored::{
138    Colorize,
139    ColoredString
140};
141use chrono::Utc;
142use log::Level;
143use std::io::Write;
144
145/// Make sure that the loglevel is in color, even though not using pretty_env logger
146pub fn color_log(level : &Level) -> ColoredString {
147  match level {
148    Level::Error    => String::from(" ERROR!").red(),
149    Level::Warn     => String::from(" WARN  ").yellow(),
150    Level::Info     => String::from(" Info  ").green(),
151    Level::Debug    => String::from(" debug ").blue(),
152    Level::Trace    => String::from(" trace ").cyan(),
153  }
154}
155
156/// Set up the environmental (env) logger
157/// with our format
158///
159/// Ensure that the lines and module paths
160/// are printed in the logging output
161pub fn init_env_logger() {
162  env_logger::builder()
163    .format(|buf, record| {
164    writeln!( buf, "[{ts} - {level}][{module_path}:{line}] {args}",
165      ts    = Utc::now().format("%Y/%m/%d-%H:%M:%SUTC"), 
166      level = color_log(&record.level()),
167      module_path = record.module_path().unwrap_or("<unknown>"),
168      line  = record.line().unwrap_or(0),
169      args  = record.args()
170      )
171    }).init();
172}
173
174//-------------------------------------------------
175// Build the python library
176
177#[cfg(feature="pybindings")]
178pub use pyo3::prelude::*; 
179#[cfg(feature="pybindings")]
180pub use pyo3::wrap_pymodule; 
181#[cfg(feature="pybindings")]
182pub use pyo3::wrap_pyfunction; 
183
184#[cfg(feature="pybindings")]
185#[pymodule]
186#[pyo3(name = "tof")]
187fn tof_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
188  use crate::tof::*;
189  m.add_class::<RBPaddleID>()?;
190  m.add_class::<TofDetectorStatus>()?;
191  m.add_class::<TofCommandCode>()?;
192  m.add_class::<TofCommand>()?;
193  m.add_class::<TofOperationMode>()?;
194  m.add_class::<BuildStrategy>()?;
195  m.add_class::<PreampBiasConfig>()?;
196  m.add_class::<RBChannelMaskConfig>()?;
197  m.add_class::<TriggerConfig>()?;
198  m.add_class::<TofRunConfig>()?;
199  m.add_class::<TofCuts>()?;
200  m.add_class::<TofAnalysis>()?;
201  m.add_class::<TofAnalysisCache>()?;
202  m.add_class::<TofAnalysisPaddleCache>()?;
203  m.add_class::<AnalysisEngineSettings>()?;
204  #[cfg(feature="tof-liftof")]
205  m.add_class::<PyMasterTrigger>()?;
206  m.add_function(wrap_pyfunction!(waveform_analysis, m)?)?;
207  m.add_function(wrap_pyfunction!(to_board_id_string, m)?)?;
208  // the commands
209  m.add_function(wrap_pyfunction!(start_run, m)?)?;
210  m.add_function(wrap_pyfunction!(stop_run, m)?)?;
211  m.add_function(wrap_pyfunction!(restart_liftofrb, m)?)?;
212  m.add_function(wrap_pyfunction!(enable_verification_run, m)?)?;
213  m.add_function(wrap_pyfunction!(shutdown_all_rbs, m)?)?;
214  m.add_function(wrap_pyfunction!(shutdown_rat, m)?)?;
215  m.add_function(wrap_pyfunction!(shutdown_ratpair, m)?)?;
216  m.add_function(wrap_pyfunction!(shutdown_rb, m)?)?;
217  m.add_function(wrap_pyfunction!(shutdown_tofcpu, m)?)?;
218  m.add_function(wrap_pyfunction!(run_action_alfa, m)?)?;
219  m.add_function(wrap_pyfunction!(run_action_bravo, m)?)?;
220  m.add_function(wrap_pyfunction!(run_action_charlie, m)?)?;
221  m.add_function(wrap_pyfunction!(run_action_whiskey, m)?)?;
222  m.add_function(wrap_pyfunction!(run_action_tango, m)?)?;
223  m.add_function(wrap_pyfunction!(run_action_foxtrott, m)?)?;
224  m.add_function(wrap_pyfunction!(request_liftof_settings, m)?)?;
225  m.add_function(wrap_pyfunction!(restore_default_config, m)?)?;
226  m.add_function(wrap_pyfunction!(apply_settings_diff, m)?)?;
227  Ok(())
228}
229
230#[cfg(feature="pybindings")]
231#[pymodule]
232#[pyo3(name = "tracker")]
233fn tracker_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
234  use crate::tracker::*;
235  //m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
236  m.add_function(wrap_pyfunction!(strip_lines, m)?)?;
237  m.add_class::<TrackerOnlineCalibration>()?;
238  Ok(())
239}
240
241#[cfg(feature="pybindings")]
242#[pymodule]
243#[pyo3(name = "calibration")]
244fn calibration_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
245  use crate::calibration::*;
246  m.add_class::<RBCalibrations>()?;
247  m.add_class::<TrackerOfflineCalibration>()?;
248  Ok(())
249}
250
251#[cfg(feature="pybindings")]
252#[pymodule]
253#[pyo3(name = "events")]
254fn events_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
255  use crate::events::*;
256  m.add_class::<McHit>()?;
257  m.add_class::<McEvent>()?;
258  m.add_class::<McTrack>()?;
259  m.add_class::<McTree>()?;
260  m.add_class::<TofHit>()?;
261  m.add_class::<TrackerHit>()?;
262  m.add_class::<RBEventHeader>()?;
263  m.add_class::<RBEvent>()?;
264  m.add_class::<RBWaveform>()?;
265  m.add_class::<EventStatus>()?;
266  m.add_class::<DataType>()?;
267  m.add_class::<TofEvent>()?;
268  m.add_class::<TrackerDAQEvent>()?;
269  m.add_class::<TrackerDAQEventPacket>()?;
270  m.add_class::<TelemetryEvent>()?;
271  m.add_function(wrap_pyfunction!(strip_id, m)?)?;
272  m.add_class::<EventQuality>()?;
273  m.add_class::<TriggerType>()?;
274  m.add_class::<LTBThreshold>()?;
275  m.add_function(wrap_pyfunction!(mt_event_get_timestamp_abs48,m)?)?;
276  Ok(())
277}
278
279#[cfg(feature="pybindings")]
280#[pymodule]
281#[pyo3(name = "packets")]
282fn packets_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
283  use crate::packets::*;
284  m.add_class::<TofPacketType>()?;
285  m.add_class::<TofPacket>()?;
286  m.add_class::<TelemetryPacketType>()?;
287  m.add_class::<TelemetryPacket>()?;
288  m.add_class::<TelemetryPacketHeader>()?;
289  m.add_class::<TrackerHeader>()?;
290  // FIXME - this is monitoring data, we should 
291  // move this over to the monitoring part of the 
292  // code and they should become MoniData/Series 
293  m.add_class::<PduChannel>()?;
294  m.add_class::<Pac1934>()?;
295  m.add_class::<PduHKPacket>()?;
296  m.add_function(wrap_pyfunction!(make_systime,m)?)?;
297  m.add_class::<TrackerEventIDEchoPacket>()?;
298  m.add_class::<TrackerTempLeakPacket>()?;
299  m.add_class::<TrackerDAQTempPacket>()?;
300  m.add_class::<TrackerDAQHSKPacket>()?;
301  Ok(())
302}
303
304#[cfg(feature="pybindings")]
305#[pymodule]
306#[pyo3(name = "io")]
307fn io_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
308  use crate::io::*;
309  use crate::io::caraspace::*;
310  #[cfg(feature="root")]
311  use crate::io::root_reader::read_example;
312  use crate::io::caraspace::frame::get_all_telemetry_event_names;
313  #[cfg(feature="root")]
314  m.add_function(wrap_pyfunction!(read_example, m)?)?;
315  m.add_function(wrap_pyfunction!(get_all_telemetry_event_names, m)?)?;
316  m.add_function(wrap_pyfunction!(get_runfilename, m)?)?;
317  m.add_function(wrap_pyfunction!(get_califilename, m)?)?;
318  m.add_function(wrap_pyfunction!(list_path_contents_sorted_py, m)?)?;
319  m.add_function(wrap_pyfunction!(get_utc_timestamp, m)?)?;
320  m.add_function(wrap_pyfunction!(get_utc_date, m)?)?;
321  m.add_function(wrap_pyfunction!(get_rundata_from_file, m)?)?;
322  m.add_function(wrap_pyfunction!(get_datetime, m)?)?;
323  m.add_function(wrap_pyfunction!(get_utc_timestamp_from_unix, m)?)?;
324  m.add_function(wrap_pyfunction!(get_unix_timestamp, m)?)?;
325  m.add_function(wrap_pyfunction!(get_unix_timestamp_from_telemetry, m)?)?;
326  // these are the config file manipulators
327  m.add_function(wrap_pyfunction!(apply_diff_to_file_py, m)?)?;
328  m.add_function(wrap_pyfunction!(compress_toml_py, m)?)?;
329  m.add_function(wrap_pyfunction!(decompress_toml_py, m)?)?;
330  m.add_function(wrap_pyfunction!(create_compressed_diff_py, m)?)?;
331
332  m.add_class::<CRFrameObject>()?;
333  m.add_class::<CRFrameObjectType>()?;
334  m.add_class::<CRFrame>()?;
335  m.add_class::<DataSourceKind>()?;
336  m.add_class::<CRReader>()?;
337  m.add_class::<CRWriter>()?;
338  m.add_class::<TofPacketReader>()?;
339  m.add_class::<TofPacketWriter>()?;
340  m.add_class::<TelemetryPacketReader>()?;
341  m.add_class::<TelemetryPacketWriter>()?;
342  //m.add_class::<PyDataSource>()?;
343  Ok(())
344}
345
346#[cfg(feature="pybindings")]
347#[pymodule]
348#[pyo3(name = "monitoring")]
349fn monitoring_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
350  use crate::monitoring::*;
351  m.add_class::<EventBuilderHB>()?;
352  m.add_class::<EventBuilderHBSeries>()?;
353  m.add_class::<DataSinkHB>()?;
354  m.add_class::<DataSinkHBSeries>()?;
355  m.add_class::<MasterTriggerHB>()?;
356  m.add_class::<MasterTriggerHBSeries>()?;
357  m.add_class::<PAMoniData>()?;
358  m.add_class::<PAMoniDataSeries>()?;
359  m.add_class::<PBMoniData>()?;
360  m.add_class::<PBMoniDataSeries>()?;
361  m.add_class::<MtbMoniData>()?;
362  m.add_class::<MtbMoniDataSeries>()?;
363  m.add_class::<LTBMoniData>()?;
364  m.add_class::<LTBMoniDataSeries>()?;
365  m.add_class::<RBMoniData>()?;
366  m.add_class::<RBMoniDataSeries>()?;
367  m.add_class::<CPUMoniData>()?;
368  m.add_class::<CPUMoniDataSeries>()?;
369  m.add_class::<SipPresMoniData>()?;
370  m.add_class::<SipPresMoniDataSeries>()?;
371  m.add_class::<SipPosMoniData>()?;
372  m.add_class::<SipPosMoniDataSeries>()?;
373  m.add_class::<SipTimeMoniData>()?;
374  m.add_class::<SipTimeMoniDataSeries>()?;
375  m.add_class::<TrackerGpsMoniData>()?;
376  m.add_class::<TrackerGpsMoniDataSeries>()?;
377  m.add_class::<GcuEvBldStatsMoniData>()?;
378  m.add_class::<GcuEvBldStatsMoniDataSeries>()?;
379  m.add_class::<CoolingMoniData>()?;
380  m.add_class::<CoolingMoniDataSeries>()?;
381  m.add_class::<RunStatistics>()?;
382  m.add_class::<WastieMoniData>()?;
383  m.add_class::<WastieMoniDataSeries>()?;
384  Ok(())
385}
386
387#[cfg(feature="pybindings")]
388#[pymodule]
389#[pyo3(name = "stats")]
390fn stats_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
391  //use crate::io::*;
392  use crate::stats::gamma_pdf_py;
393  use crate::stats::mean_py;
394  m.add_function(wrap_pyfunction!(gamma_pdf_py, m)?)?;
395  m.add_function(wrap_pyfunction!(mean_py, m)?)?;
396  Ok(())
397}
398
399#[cfg(feature="pybindings")]
400#[pymodule]
401#[pyo3(name = "algo")]
402fn algo_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
403  //use crate::io::*;
404  use crate::tof::algorithms::*;
405  m.add_function(wrap_pyfunction!(get_max_value_idx_py, m)?)?;
406  m.add_function(wrap_pyfunction!(interpolate_time_py, m)?)?;
407  m.add_function(wrap_pyfunction!(fit_sine_simple_py, m)?)?;
408  Ok(())
409}
410
411#[cfg(feature="database")]
412#[cfg(feature="pybindings")]
413#[pymodule]
414#[pyo3(name = "db")]
415fn db_py<'_py>(m: &Bound<'_py, PyModule>) -> PyResult<()> {
416  use crate::database::*;
417  m.add_class::<ReadoutBoard>()?;
418  m.add_class::<RAT>()?;
419  m.add_class::<TofPaddle>()?;
420  m.add_class::<TrackerStrip>()?;
421  m.add_class::<TrackerStripMask>()?;
422  m.add_class::<TrackerStripPedestal>()?;
423  m.add_class::<TrackerStripTransferFunction>()?;
424  m.add_class::<TrackerStripCmnNoise>()?;
425  m.add_class::<TrackerStripGain>()?;
426  m.add_class::<TrackerStripPulse>()?;
427  m.add_class::<TofPaddleTimingConstant>()?;
428  m.add_class::<TrackerCalibrationFile>()?;
429  m.add_class::<TrackerCalibrationFileType>()?;
430  m.add_function(wrap_pyfunction!(load_calibration_db_elena, m)?)?;
431  m.add_function(wrap_pyfunction!(get_all_rbids_in_db, m)?)?;
432  m.add_function(wrap_pyfunction!(get_all_pbids_in_db, m)?)?;
433  m.add_function(wrap_pyfunction!(get_hid_vid_maps, m)?)?;
434  m.add_function(wrap_pyfunction!(get_vid_hid_maps, m)?)?;
435  m.add_function(wrap_pyfunction!(get_rbids_for_pbid, m)?)?;
436  m.add_function(wrap_pyfunction!(get_dsi_j_ch_pid_map_py, m)?)?;
437  m.add_function(wrap_pyfunction!(get_rbid_pbchannel_pid_map_py, m)?)?;
438  m.add_function(wrap_pyfunction!(load_calibration_db_elena, m)?)?;
439  m.add_function(wrap_pyfunction!(create_trk_mask_table, m)?)?;
440  m.add_function(wrap_pyfunction!(create_trk_pedestal_table, m)?)?;
441  m.add_function(wrap_pyfunction!(create_trk_transfer_fn_table, m)?)?;
442  m.add_function(wrap_pyfunction!(create_trk_pulse_table, m)?)?;
443  m.add_function(wrap_pyfunction!(create_trk_gain_table, m)?)?;
444  Ok(())
445}
446
447#[cfg(feature="pybindings")]
448#[pyfunction]
449fn get_version() -> &'static str {
450  return VERSION;
451}
452
453/// Major version of the package 
454/// from the version string
455/// (Major.Minor.Patch)
456#[cfg(feature="pybindings")]
457#[pyfunction]
458fn get_version_major() -> u8 {
459  return VERSION_MAJ.parse().unwrap();
460}
461
462/// Minor version of the package 
463/// from the version string
464/// (Major.Minor.Patch)
465#[cfg(feature="pybindings")]
466#[pyfunction]
467fn get_version_minor() -> u8 {
468  return VERSION_MIN.parse().unwrap();
469}
470
471/// Patch level of the package 
472/// from the version string
473/// (Major.Minor.Patch)
474#[cfg(feature="pybindings")]
475#[pyfunction]
476fn get_version_patch() -> u8 {
477  return VERSION_PCH.parse().unwrap();
478}
479
480/// Check if the version is at least the 
481/// required, given string following the 
482/// format MAJOR.MINOR.PATCH 
483#[cfg(feature="pybindings")]
484#[pyfunction]
485fn version_at_least(version_string : String) -> bool {
486  let parts: Vec<u32> = version_string.split('.')
487    .map(|s| s.parse().expect("Input is not a valid version string. Please follow the MAJOR.MINOR.PATCH format!"))
488    .collect();
489  let major : u8 = parts[0] as u8;
490  let minor : u8 = parts[1] as u8;
491  let patch : u8 = parts[2] as u8;
492  if major < get_version_major() {
493    return true;
494  }
495  if major > get_version_major() {
496    println!("Required major version of {} is larger than current version {}", major, VERSION);
497    return false;
498  }
499  if major == get_version_major() {
500    if minor < get_version_minor() {
501      return true;
502    }
503    if minor > get_version_minor() {
504      println!("Required minor version of {} is larger than current version {}", minor, VERSION);
505      return false;
506    }
507    if minor == get_version_minor() {
508      if patch > get_version_patch() {
509        println!("Required patch version of {} is larger than current version {}", patch, VERSION);
510        return false 
511      }
512    }
513  } 
514  true
515}
516
517#[cfg(feature="pybindings")]
518pyo3::create_exception!(gondola_core_py, PyMasterTriggerError, pyo3::exceptions::PyException);
519#[cfg(feature="pybindings")]
520pyo3::create_exception!(gondola_core_py, PyRunError, pyo3::exceptions::PyException);
521#[cfg(feature="pybindings")]
522pyo3::create_exception!(gondola_core_py, PyTofError, pyo3::exceptions::PyException);
523#[cfg(feature="pybindings")]
524pyo3::create_exception!(gondola_core_py, PyStagingError, pyo3::exceptions::PyException);
525#[cfg(feature="pybindings")]
526pyo3::create_exception!(gondola_core_py, PySensorError, pyo3::exceptions::PyException);
527#[cfg(feature="pybindings")]
528pyo3::create_exception!(gondola_core_py, PyUserError, pyo3::exceptions::PyException);
529#[cfg(feature="pybindings")]
530pyo3::create_exception!(gondola_core_py, PyCalibrationError, pyo3::exceptions::PyException);
531#[cfg(feature="pybindings")]
532pyo3::create_exception!(gondola_core_py, PyWaveformError, pyo3::exceptions::PyException);
533#[cfg(feature="pybindings")]
534pyo3::create_exception!(gondola_core_py, PyIPBusError, pyo3::exceptions::PyException);
535#[cfg(feature="pybindings")]
536pyo3::create_exception!(gondola_core_py, PySerializationError, pyo3::exceptions::PyException);
537#[cfg(feature="pybindings")]
538pyo3::create_exception!(gondola_core_py, PyAnalysisError, pyo3::exceptions::PyException); 
539
540#[macro_export]
541macro_rules! pythonize_error {
542  ($name:ident, $pyname:ident) => {
543
544    impl From<$name> for PyErr {
545      fn from(err: $name) -> PyErr {
546        // You can use a standard Python exception if you prefer, 
547        // e.g., pyo3::exceptions::PyValueError::new_err(...)
548        // But mapping to your custom PyAnalysisError is usually better. 
549        $pyname::new_err(format!("<GondolaCoreException: {}>", err))
550      }
551    }
552  }
553}
554
555#[cfg(feature="pybindings")]
556pythonize_error!(SerializationError, PySerializationError);
557#[cfg(feature="pybindings")]
558pythonize_error!(AnalysisError, PyAnalysisError);
559#[cfg(feature="pybindings")]
560pythonize_error!(CalibrationError, PyCalibrationError);
561
562
563/// Python API to rust version of tof-dataclasses.
564///
565/// Currently, this contains only the analysis 
566/// functions
567#[cfg(feature="pybindings")]
568#[pymodule]
569#[pyo3(name = "gondola_core")]
570fn gondola_core_py<'_py>(m : &Bound<'_py, PyModule>) -> PyResult<()> { //: Python<'_>, m: &PyModule) -> PyResult<()> {
571  pyo3_log::init();
572  m.add_function(wrap_pyfunction!(get_version, m)?)?;
573  m.add_function(wrap_pyfunction!(get_version_major, m)?)?;
574  m.add_function(wrap_pyfunction!(get_version_minor, m)?)?;
575  m.add_function(wrap_pyfunction!(get_version_patch, m)?)?;
576  m.add_function(wrap_pyfunction!(version_at_least, m)?)?;
577  m.add_wrapped(wrap_pymodule!(events_py))?;
578  m.add_wrapped(wrap_pymodule!(monitoring_py))?;
579  m.add_wrapped(wrap_pymodule!(packets_py))?;
580  m.add_wrapped(wrap_pymodule!(tof_py))?;
581  m.add_wrapped(wrap_pymodule!(tracker_py))?;
582  m.add_wrapped(wrap_pymodule!(io_py))?;
583  m.add_wrapped(wrap_pymodule!(db_py))?;
584  m.add_wrapped(wrap_pymodule!(stats_py))?;
585  m.add_wrapped(wrap_pymodule!(algo_py))?;
586  m.add_wrapped(wrap_pymodule!(calibration_py))?;
587  //m.add("SerializationError",  pyo3::types::PyAnyMethods::<PySerializationError>::get_type(m))?;
588  //m.add("AnalysisError",  pyo3::types::PyAnyMethods::<PyAnalysisError>::get_type(m))?;
589  Ok(())
590}
591
592
593
594