edge_impulse_runner/ffi/
wrapper.rs

1//! Safe Rust wrapper for Edge Impulse FFI bindings
2//!
3//! This module provides safe Rust bindings for the Edge Impulse C++ SDK,
4//! allowing you to run inference on trained models from Rust applications.
5
6use std::error::Error;
7use std::fmt;
8
9impl fmt::Display for ClassificationResult {
10    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11        write!(f, "{}: {:.4}", self.label, self.value)
12    }
13}
14
15impl fmt::Display for BoundingBox {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        write!(
18            f,
19            "{}: {:.4} (x={}, y={}, w={}, h={})",
20            self.label, self.value, self.x, self.y, self.width, self.height
21        )
22    }
23}
24
25impl fmt::Display for TimingResult {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(
28            f,
29            "Timing: dsp={} ms, classification={} ms, anomaly={} ms",
30            self.dsp, self.classification, self.anomaly
31        )
32    }
33}
34
35/// Error type for Edge Impulse operations
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum EdgeImpulseError {
38    Ok,
39    ShapesDontMatch,
40    Canceled,
41    MemoryAllocationFailed,
42    OutOfMemory,
43    InputTensorWasNull,
44    OutputTensorWasNull,
45    AllocatedTensorWasNull,
46    TfliteError,
47    TfliteArenaAllocFailed,
48    ReadSensor,
49    MinSizeRatio,
50    MaxSizeRatio,
51    OnlySupportImages,
52    ModelInputTensorWasNull,
53    ModelOutputTensorWasNull,
54    UnsupportedInferencingEngine,
55    AllocWhileCacheLocked,
56    NoValidImpulse,
57    Other,
58}
59
60#[cfg(feature = "ffi")]
61impl From<edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR> for EdgeImpulseError {
62    fn from(error: edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR) -> Self {
63        match error {
64            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OK => EdgeImpulseError::Ok,
65            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_ERROR_SHAPES_DONT_MATCH => {
66                EdgeImpulseError::ShapesDontMatch
67            }
68            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_CANCELED => EdgeImpulseError::Canceled,
69            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_ALLOC_FAILED => EdgeImpulseError::MemoryAllocationFailed,
70            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OUT_OF_MEMORY => EdgeImpulseError::OutOfMemory,
71            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_INPUT_TENSOR_WAS_NULL => {
72                EdgeImpulseError::InputTensorWasNull
73            }
74            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OUTPUT_TENSOR_WAS_NULL => {
75                EdgeImpulseError::OutputTensorWasNull
76            }
77            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_TFLITE_ERROR => EdgeImpulseError::TfliteError,
78            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_TFLITE_ARENA_ALLOC_FAILED => {
79                EdgeImpulseError::TfliteArenaAllocFailed
80            }
81            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_DSP_ERROR => EdgeImpulseError::ReadSensor,
82            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_INVALID_SIZE => EdgeImpulseError::MinSizeRatio,
83            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_ONLY_SUPPORTED_FOR_IMAGES => {
84                EdgeImpulseError::OnlySupportImages
85            }
86            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_UNSUPPORTED_INFERENCING_ENGINE => {
87                EdgeImpulseError::UnsupportedInferencingEngine
88            }
89            edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_INFERENCE_ERROR => EdgeImpulseError::NoValidImpulse,
90            _ => EdgeImpulseError::Other,
91        }
92    }
93}
94
95impl fmt::Display for EdgeImpulseError {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            EdgeImpulseError::Ok => write!(f, "Operation completed successfully"),
99            EdgeImpulseError::ShapesDontMatch => {
100                write!(f, "Input shapes don't match expected dimensions")
101            }
102            EdgeImpulseError::Canceled => write!(f, "Operation was canceled"),
103            EdgeImpulseError::MemoryAllocationFailed => write!(f, "Memory allocation failed"),
104            EdgeImpulseError::OutOfMemory => write!(f, "Out of memory"),
105            EdgeImpulseError::InputTensorWasNull => write!(f, "Input tensor was null"),
106            EdgeImpulseError::OutputTensorWasNull => write!(f, "Output tensor was null"),
107            EdgeImpulseError::AllocatedTensorWasNull => write!(f, "Allocated tensor was null"),
108            EdgeImpulseError::TfliteError => write!(f, "TensorFlow Lite error"),
109            EdgeImpulseError::TfliteArenaAllocFailed => {
110                write!(f, "TensorFlow Lite arena allocation failed")
111            }
112            EdgeImpulseError::ReadSensor => write!(f, "Error reading sensor data"),
113            EdgeImpulseError::MinSizeRatio => write!(f, "Minimum size ratio not met"),
114            EdgeImpulseError::MaxSizeRatio => write!(f, "Maximum size ratio exceeded"),
115            EdgeImpulseError::OnlySupportImages => write!(f, "Only image input is supported"),
116            EdgeImpulseError::ModelInputTensorWasNull => write!(f, "Model input tensor was null"),
117            EdgeImpulseError::ModelOutputTensorWasNull => write!(f, "Model output tensor was null"),
118            EdgeImpulseError::UnsupportedInferencingEngine => {
119                write!(f, "Unsupported inferencing engine")
120            }
121            EdgeImpulseError::AllocWhileCacheLocked => {
122                write!(f, "Allocation attempted while cache is locked")
123            }
124            EdgeImpulseError::NoValidImpulse => write!(f, "No valid impulse found"),
125            EdgeImpulseError::Other => write!(f, "Unknown error occurred"),
126        }
127    }
128}
129
130impl Error for EdgeImpulseError {}
131
132/// Result type for Edge Impulse operations
133pub type EdgeImpulseResult<T> = Result<T, EdgeImpulseError>;
134
135/// Opaque handle for Edge Impulse operations
136pub struct EdgeImpulseHandle {
137    #[cfg(feature = "ffi")]
138    handle: *mut edge_impulse_ffi_rs::bindings::ei_impulse_handle_t,
139}
140
141impl EdgeImpulseHandle {
142    /// Create a new Edge Impulse handle
143    pub fn new() -> EdgeImpulseResult<Self> {
144        #[cfg(feature = "ffi")]
145        {
146            let handle_ptr =
147                std::ptr::null_mut::<edge_impulse_ffi_rs::bindings::ei_impulse_handle_t>();
148            let result = unsafe { edge_impulse_ffi_rs::bindings::ei_ffi_init_impulse(handle_ptr) };
149            let error = EdgeImpulseError::from(result);
150            if error != EdgeImpulseError::Ok {
151                return Err(error);
152            }
153
154            Ok(Self { handle: handle_ptr })
155        }
156
157        #[cfg(not(feature = "ffi"))]
158        {
159            Err(EdgeImpulseError::Other)
160        }
161    }
162}
163
164impl Drop for EdgeImpulseHandle {
165    fn drop(&mut self) {
166        // Cleanup if needed
167    }
168}
169
170/// Signal structure for holding audio or sensor data
171pub struct Signal {
172    #[cfg(feature = "ffi")]
173    c_signal: Box<edge_impulse_ffi_rs::bindings::ei_signal_t>,
174}
175
176impl Signal {
177    /// Create a new signal from raw data (f32 slice) using the SDK's signal_from_buffer
178    pub fn from_raw_data(_data: &[f32]) -> EdgeImpulseResult<Self> {
179        #[cfg(feature = "ffi")]
180        {
181            let mut c_signal = Box::new(edge_impulse_ffi_rs::bindings::ei_signal_t {
182                get_data: [0u64; 4], // Initialize with zeros for std::function
183                total_length: 0,
184            });
185            let result = unsafe {
186                edge_impulse_ffi_rs::bindings::ei_ffi_signal_from_buffer(
187                    _data.as_ptr(),
188                    _data.len(),
189                    &mut *c_signal,
190                )
191            };
192
193            if result == edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OK {
194                Ok(Self { c_signal })
195            } else {
196                Err(EdgeImpulseError::from(result))
197            }
198        }
199
200        #[cfg(not(feature = "ffi"))]
201        {
202            Err(EdgeImpulseError::Other)
203        }
204    }
205
206    #[cfg(feature = "ffi")]
207    pub fn as_ptr(&self) -> *mut edge_impulse_ffi_rs::bindings::ei_signal_t {
208        Box::as_ref(&self.c_signal) as *const edge_impulse_ffi_rs::bindings::ei_signal_t
209            as *mut edge_impulse_ffi_rs::bindings::ei_signal_t
210    }
211}
212
213/// Result structure for Edge Impulse inference
214pub struct InferenceResult {
215    #[cfg(feature = "ffi")]
216    result: *mut edge_impulse_ffi_rs::bindings::ei_impulse_result_t,
217}
218
219impl Default for InferenceResult {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225impl InferenceResult {
226    /// Create a new inference result
227    pub fn new() -> Self {
228        #[cfg(feature = "ffi")]
229        {
230            let result = unsafe {
231                let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::new::<
232                    edge_impulse_ffi_rs::bindings::ei_impulse_result_t,
233                >())
234                    as *mut edge_impulse_ffi_rs::bindings::ei_impulse_result_t;
235                ptr
236            };
237            Self { result }
238        }
239
240        #[cfg(not(feature = "ffi"))]
241        {
242            Self {}
243        }
244    }
245
246    /// Get a raw pointer to the underlying ei_impulse_result_t (for advanced result parsing)
247    #[cfg(feature = "ffi")]
248    pub fn as_ptr(&self) -> *const edge_impulse_ffi_rs::bindings::ei_impulse_result_t {
249        self.result as *const edge_impulse_ffi_rs::bindings::ei_impulse_result_t
250    }
251
252    /// Get a mutable raw pointer to the underlying ei_impulse_result_t (for advanced result parsing)
253    #[cfg(feature = "ffi")]
254    pub fn as_mut_ptr(&mut self) -> *mut edge_impulse_ffi_rs::bindings::ei_impulse_result_t {
255        self.result
256    }
257
258    /// Get all classification results as safe Rust structs
259    pub fn classifications(&self, _label_count: usize) -> Vec<ClassificationResult> {
260        #[cfg(feature = "ffi")]
261        {
262            unsafe {
263                let result = &*self.result;
264                (0.._label_count)
265                    .map(|i| {
266                        let c = result.classification[i];
267                        let label = if !c.label.is_null() {
268                            std::ffi::CStr::from_ptr(c.label)
269                                .to_string_lossy()
270                                .into_owned()
271                        } else {
272                            String::new()
273                        };
274                        ClassificationResult {
275                            label,
276                            value: c.value,
277                        }
278                    })
279                    .collect()
280            }
281        }
282
283        #[cfg(not(feature = "ffi"))]
284        {
285            vec![]
286        }
287    }
288
289    /// Get all bounding boxes as safe Rust structs
290    pub fn bounding_boxes(&self) -> Vec<BoundingBox> {
291        #[cfg(feature = "ffi")]
292        {
293            unsafe {
294                let result = &*self.result;
295                if result.bounding_boxes_count == 0 || result.bounding_boxes.is_null() {
296                    return vec![];
297                }
298                let bbs = std::slice::from_raw_parts(
299                    result.bounding_boxes,
300                    result.bounding_boxes_count as usize,
301                );
302                bbs.iter()
303                    .filter_map(|bb| {
304                        if bb.value == 0.0 {
305                            return None;
306                        }
307                        let label = if !bb.label.is_null() {
308                            std::ffi::CStr::from_ptr(bb.label)
309                                .to_string_lossy()
310                                .into_owned()
311                        } else {
312                            String::new()
313                        };
314                        Some(BoundingBox {
315                            label,
316                            value: bb.value,
317                            x: bb.x,
318                            y: bb.y,
319                            width: bb.width,
320                            height: bb.height,
321                        })
322                    })
323                    .collect()
324            }
325        }
326
327        #[cfg(not(feature = "ffi"))]
328        {
329            vec![]
330        }
331    }
332
333    /// Get timing information
334    pub fn timing(&self) -> TimingResult {
335        #[cfg(feature = "ffi")]
336        {
337            unsafe {
338                let result = &*self.result;
339                let t = &result.timing;
340                TimingResult {
341                    dsp: t.dsp as i32,
342                    classification: t.classification as i32,
343                    anomaly: t.anomaly as i32,
344                }
345            }
346        }
347
348        #[cfg(not(feature = "ffi"))]
349        {
350            TimingResult {
351                dsp: 0,
352                classification: 0,
353                anomaly: 0,
354            }
355        }
356    }
357}
358
359impl Drop for InferenceResult {
360    fn drop(&mut self) {
361        #[cfg(feature = "ffi")]
362        {
363            if !self.result.is_null() {
364                unsafe {
365                    std::alloc::dealloc(
366                        self.result as *mut u8,
367                        std::alloc::Layout::new::<edge_impulse_ffi_rs::bindings::ei_impulse_result_t>(
368                        ),
369                    );
370                }
371            }
372        }
373    }
374}
375
376/// Main Edge Impulse classifier
377pub struct EdgeImpulseClassifier {
378    #[cfg(feature = "ffi")]
379    initialized: bool,
380}
381
382impl Default for EdgeImpulseClassifier {
383    fn default() -> Self {
384        Self::new()
385    }
386}
387
388impl EdgeImpulseClassifier {
389    /// Create a new Edge Impulse classifier
390    pub fn new() -> Self {
391        #[cfg(feature = "ffi")]
392        {
393            Self { initialized: false }
394        }
395
396        #[cfg(not(feature = "ffi"))]
397        {
398            Self {}
399        }
400    }
401
402    /// Initialize the classifier
403    pub fn init(&mut self) -> EdgeImpulseResult<()> {
404        #[cfg(feature = "ffi")]
405        {
406            unsafe { edge_impulse_ffi_rs::bindings::ei_ffi_run_classifier_init() };
407            self.initialized = true;
408            Ok(())
409        }
410
411        #[cfg(not(feature = "ffi"))]
412        {
413            Err(EdgeImpulseError::Other)
414        }
415    }
416
417    /// Deinitialize the classifier
418    pub fn deinit(&mut self) -> EdgeImpulseResult<()> {
419        #[cfg(feature = "ffi")]
420        {
421            if self.initialized {
422                unsafe { edge_impulse_ffi_rs::bindings::ei_ffi_run_classifier_deinit() };
423                self.initialized = false;
424            }
425            Ok(())
426        }
427
428        #[cfg(not(feature = "ffi"))]
429        {
430            Ok(())
431        }
432    }
433
434    /// Run classification on signal data
435    pub fn run_classifier(
436        &self,
437        _signal: &Signal,
438        _debug: bool,
439    ) -> EdgeImpulseResult<InferenceResult> {
440        #[cfg(feature = "ffi")]
441        {
442            if !self.initialized {
443                return Err(EdgeImpulseError::Other);
444            }
445            let result = InferenceResult::new();
446            let result_code = unsafe {
447                edge_impulse_ffi_rs::bindings::ei_ffi_run_classifier(
448                    _signal.as_ptr(),
449                    result.result,
450                    if _debug { 1 } else { 0 },
451                )
452            };
453            if result_code == edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OK {
454                Ok(result)
455            } else {
456                Err(EdgeImpulseError::from(result_code))
457            }
458        }
459
460        #[cfg(not(feature = "ffi"))]
461        {
462            Err(EdgeImpulseError::Other)
463        }
464    }
465
466    /// Run continuous classification on signal data
467    pub fn run_classifier_continuous(
468        &self,
469        _signal: &Signal,
470        _debug: bool,
471        _enable_maf: bool,
472    ) -> EdgeImpulseResult<InferenceResult> {
473        #[cfg(feature = "ffi")]
474        {
475            if !self.initialized {
476                return Err(EdgeImpulseError::Other);
477            }
478            let result = InferenceResult::new();
479            let result_code = unsafe {
480                edge_impulse_ffi_rs::bindings::ei_ffi_run_classifier_continuous(
481                    _signal.as_ptr(),
482                    result.result,
483                    if _debug { 1 } else { 0 },
484                    if _enable_maf { 1 } else { 0 },
485                )
486            };
487            if result_code == edge_impulse_ffi_rs::bindings::EI_IMPULSE_ERROR::EI_IMPULSE_OK {
488                Ok(result)
489            } else {
490                Err(EdgeImpulseError::from(result_code))
491            }
492        }
493
494        #[cfg(not(feature = "ffi"))]
495        {
496            Err(EdgeImpulseError::Other)
497        }
498    }
499
500    /// Run inference on pre-processed features
501    pub fn run_inference(
502        &self,
503        _handle: &mut EdgeImpulseHandle,
504        #[cfg(feature = "ffi")] fmatrix: *mut edge_impulse_ffi_rs::bindings::ei_feature_t,
505        #[cfg(not(feature = "ffi"))] _fmatrix: *mut std::ffi::c_void,
506        _debug: bool,
507    ) -> EdgeImpulseResult<InferenceResult> {
508        #[cfg(feature = "ffi")]
509        {
510            if !self.initialized {
511                return Err(EdgeImpulseError::Other);
512            }
513            let result = InferenceResult::new();
514            let result_code = unsafe {
515                edge_impulse_ffi_rs::bindings::ei_ffi_run_inference(
516                    _handle.handle,
517                    fmatrix,
518                    result.result,
519                    if _debug { 1 } else { 0 },
520                )
521            };
522            let error = EdgeImpulseError::from(result_code);
523            if error == EdgeImpulseError::Ok {
524                Ok(result)
525            } else {
526                Err(error)
527            }
528        }
529
530        #[cfg(not(feature = "ffi"))]
531        {
532            Err(EdgeImpulseError::Other)
533        }
534    }
535}
536
537impl Drop for EdgeImpulseClassifier {
538    fn drop(&mut self) {
539        let _ = self.deinit();
540    }
541}
542
543// Model metadata constants are available from edge_impulse_ffi_rs::model_metadata
544
545/// Helper functions to access model metadata
546pub struct ModelMetadata;
547
548#[derive(Debug, Clone)]
549pub struct ModelMetadataInfo {
550    pub input_width: usize,
551    pub input_height: usize,
552    pub input_frames: usize,
553    pub label_count: usize,
554    pub project_name: &'static str,
555    pub project_owner: &'static str,
556    pub project_id: usize,
557    pub deploy_version: usize,
558    pub sensor: i32,
559    pub inferencing_engine: usize,
560    pub interval_ms: usize,
561    pub frequency: usize,
562    pub slice_size: usize,
563    pub has_anomaly: bool,
564    pub has_object_detection: bool,
565    pub has_object_tracking: bool,
566    pub raw_sample_count: usize,
567    pub raw_samples_per_frame: usize,
568    pub input_features_count: usize,
569}
570
571#[derive(Debug, Clone)]
572pub struct ClassificationResult {
573    pub label: String,
574    pub value: f32,
575}
576
577#[derive(Debug, Clone)]
578pub struct BoundingBox {
579    pub label: String,
580    pub value: f32,
581    pub x: u32,
582    pub y: u32,
583    pub width: u32,
584    pub height: u32,
585}
586
587#[derive(Debug, Clone)]
588pub struct TimingResult {
589    pub dsp: i32,
590    pub classification: i32,
591    pub anomaly: i32,
592}
593
594impl fmt::Display for ModelMetadataInfo {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        writeln!(f, "Model Metadata:")?;
597        writeln!(
598            f,
599            "  Project: {} (ID: {})",
600            self.project_name, self.project_id
601        )?;
602        writeln!(f, "  Owner: {}", self.project_owner)?;
603        writeln!(f, "  Deploy version: {}", self.deploy_version)?;
604        writeln!(
605            f,
606            "  Input: {}x{} frames: {}",
607            self.input_width, self.input_height, self.input_frames
608        )?;
609        writeln!(f, "  Label count: {}", self.label_count)?;
610        writeln!(f, "  Sensor: {}", self.sensor)?;
611        writeln!(f, "  Inferencing engine: {}", self.inferencing_engine)?;
612        writeln!(f, "  Interval (ms): {}", self.interval_ms)?;
613        writeln!(f, "  Frequency: {}", self.frequency)?;
614        writeln!(f, "  Slice size: {}", self.slice_size)?;
615        writeln!(f, "  Has anomaly: {}", self.has_anomaly)?;
616        writeln!(f, "  Has object detection: {}", self.has_object_detection)?;
617        writeln!(f, "  Has object tracking: {}", self.has_object_tracking)?;
618        writeln!(f, "  Raw sample count: {}", self.raw_sample_count)?;
619        writeln!(f, "  Raw samples per frame: {}", self.raw_samples_per_frame)?;
620        writeln!(f, "  Input features count: {}", self.input_features_count)?;
621        Ok(())
622    }
623}
624
625impl ModelMetadata {
626    /// Get the model's required input width
627    pub fn input_width() -> usize {
628        #[cfg(feature = "ffi")]
629        {
630            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_INPUT_WIDTH
631        }
632        #[cfg(not(feature = "ffi"))]
633        {
634            0
635        }
636    }
637
638    /// Get the model's required input height
639    pub fn input_height() -> usize {
640        #[cfg(feature = "ffi")]
641        {
642            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_INPUT_HEIGHT
643        }
644        #[cfg(not(feature = "ffi"))]
645        {
646            0
647        }
648    }
649
650    /// Get the model's required input frame size (width * height)
651    pub fn input_frame_size() -> usize {
652        Self::input_width() * Self::input_height()
653    }
654
655    /// Get the number of input frames
656    pub fn input_frames() -> usize {
657        #[cfg(feature = "ffi")]
658        {
659            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_INPUT_FRAMES
660        }
661        #[cfg(not(feature = "ffi"))]
662        {
663            0
664        }
665    }
666
667    /// Get the number of labels
668    pub fn label_count() -> usize {
669        #[cfg(feature = "ffi")]
670        {
671            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_LABEL_COUNT
672        }
673        #[cfg(not(feature = "ffi"))]
674        {
675            0
676        }
677    }
678
679    /// Get the project name
680    pub fn project_name() -> &'static str {
681        #[cfg(feature = "ffi")]
682        {
683            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_PROJECT_NAME
684        }
685        #[cfg(not(feature = "ffi"))]
686        {
687            ""
688        }
689    }
690
691    /// Get the project owner
692    pub fn project_owner() -> &'static str {
693        #[cfg(feature = "ffi")]
694        {
695            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_PROJECT_OWNER
696        }
697        #[cfg(not(feature = "ffi"))]
698        {
699            ""
700        }
701    }
702
703    /// Get the project ID
704    pub fn project_id() -> usize {
705        #[cfg(feature = "ffi")]
706        {
707            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_PROJECT_ID
708        }
709        #[cfg(not(feature = "ffi"))]
710        {
711            0
712        }
713    }
714
715    /// Get the deploy version
716    pub fn deploy_version() -> usize {
717        #[cfg(feature = "ffi")]
718        {
719            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_PROJECT_DEPLOY_VERSION
720        }
721        #[cfg(not(feature = "ffi"))]
722        {
723            0
724        }
725    }
726
727    /// Get the sensor type
728    pub fn sensor() -> i32 {
729        #[cfg(feature = "ffi")]
730        {
731            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_SENSOR
732        }
733        #[cfg(not(feature = "ffi"))]
734        {
735            0
736        }
737    }
738
739    /// Get the inferencing engine
740    pub fn inferencing_engine() -> usize {
741        #[cfg(feature = "ffi")]
742        {
743            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_INFERENCING_ENGINE
744        }
745        #[cfg(not(feature = "ffi"))]
746        {
747            0
748        }
749    }
750
751    /// Get the model's interval in ms
752    pub fn interval_ms() -> usize {
753        #[cfg(feature = "ffi")]
754        {
755            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_INTERVAL_MS
756        }
757        #[cfg(not(feature = "ffi"))]
758        {
759            0
760        }
761    }
762
763    /// Get the model's frequency
764    pub fn frequency() -> usize {
765        #[cfg(feature = "ffi")]
766        {
767            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_FREQUENCY
768        }
769        #[cfg(not(feature = "ffi"))]
770        {
771            0
772        }
773    }
774
775    /// Get the model's slice size
776    pub fn slice_size() -> usize {
777        #[cfg(feature = "ffi")]
778        {
779            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_SLICE_SIZE
780        }
781        #[cfg(not(feature = "ffi"))]
782        {
783            0
784        }
785    }
786
787    /// Whether the model has anomaly detection
788    pub fn has_anomaly() -> bool {
789        #[cfg(feature = "ffi")]
790        {
791            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_HAS_ANOMALY != 0
792        }
793        #[cfg(not(feature = "ffi"))]
794        {
795            false
796        }
797    }
798
799    /// Whether the model has object detection
800    pub fn has_object_detection() -> bool {
801        #[cfg(feature = "ffi")]
802        {
803            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_OBJECT_DETECTION != 0
804        }
805        #[cfg(not(feature = "ffi"))]
806        {
807            false
808        }
809    }
810
811    /// Whether the model has object tracking
812    pub fn has_object_tracking() -> bool {
813        #[cfg(feature = "ffi")]
814        {
815            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_OBJECT_TRACKING_ENABLED != 0
816        }
817        #[cfg(not(feature = "ffi"))]
818        {
819            false
820        }
821    }
822
823    /// Get the model's raw sample count
824    pub fn raw_sample_count() -> usize {
825        #[cfg(feature = "ffi")]
826        {
827            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_RAW_SAMPLE_COUNT
828        }
829        #[cfg(not(feature = "ffi"))]
830        {
831            0
832        }
833    }
834
835    /// Get the model's raw samples per frame
836    pub fn raw_samples_per_frame() -> usize {
837        #[cfg(feature = "ffi")]
838        {
839            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME
840        }
841        #[cfg(not(feature = "ffi"))]
842        {
843            0
844        }
845    }
846
847    /// Get the model's input feature count
848    pub fn input_features_count() -> usize {
849        #[cfg(feature = "ffi")]
850        {
851            edge_impulse_ffi_rs::model_metadata::EI_CLASSIFIER_NN_INPUT_FRAME_SIZE
852        }
853        #[cfg(not(feature = "ffi"))]
854        {
855            0
856        }
857    }
858
859    pub fn get() -> ModelMetadataInfo {
860        ModelMetadataInfo {
861            input_width: Self::input_width(),
862            input_height: Self::input_height(),
863            input_frames: Self::input_frames(),
864            label_count: Self::label_count(),
865            project_name: Self::project_name(),
866            project_owner: Self::project_owner(),
867            project_id: Self::project_id(),
868            deploy_version: Self::deploy_version(),
869            sensor: Self::sensor(),
870            inferencing_engine: Self::inferencing_engine(),
871            interval_ms: Self::interval_ms(),
872            frequency: Self::frequency(),
873            slice_size: Self::slice_size(),
874            has_anomaly: Self::has_anomaly(),
875            has_object_detection: Self::has_object_detection(),
876            has_object_tracking: Self::has_object_tracking(),
877            raw_sample_count: Self::raw_sample_count(),
878            raw_samples_per_frame: Self::raw_samples_per_frame(),
879            input_features_count: Self::input_features_count(),
880        }
881    }
882}
883
884#[cfg(test)]
885mod tests {
886    use super::*;
887
888    #[test]
889    fn test_ffi_initialization() {
890        let mut classifier = EdgeImpulseClassifier::new();
891        // This will fail without the ffi feature, but that's expected
892        let _ = classifier.init();
893        let _ = classifier.deinit();
894    }
895}