Skip to main content

vimba/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::{convert::TryInto, pin::Pin};
5
6use machine_vision_formats as formats;
7
8use vmbc_sys::{
9    VmbCameraInfo_t, VmbErrorType, VmbFeaturePersistSettings_t, VmbFrame_t, VmbFrameCallback,
10    VmbFrameStatusType, VmbHandle_t, VmbVersionInfo_t,
11};
12
13fn err_str(err: i32) -> &'static str {
14    use VmbErrorType::*;
15    #[expect(non_upper_case_globals)]
16    match err {
17        VmbErrorSuccess => "VmbErrorSuccess",
18        VmbErrorInternalFault => "VmbErrorInternalFault",
19        VmbErrorApiNotStarted => "VmbErrorApiNotStarted",
20        VmbErrorNotFound => "VmbErrorNotFound",
21        VmbErrorBadHandle => "VmbErrorBadHandle",
22        VmbErrorDeviceNotOpen => "VmbErrorDeviceNotOpen",
23        VmbErrorInvalidAccess => "VmbErrorInvalidAccess",
24        VmbErrorBadParameter => "VmbErrorBadParameter",
25        VmbErrorStructSize => "VmbErrorStructSize",
26        VmbErrorMoreData => "VmbErrorMoreData",
27        VmbErrorWrongType => "VmbErrorWrongType",
28        VmbErrorInvalidValue => "VmbErrorInvalidValue",
29        VmbErrorTimeout => "VmbErrorTimeout",
30        VmbErrorOther => "VmbErrorOther",
31        VmbErrorResources => "VmbErrorResources",
32        VmbErrorInvalidCall => "VmbErrorInvalidCall",
33        VmbErrorNoTL => "VmbErrorNoTL",
34        VmbErrorNotImplemented => "VmbErrorNotImplemented",
35        VmbErrorNotSupported => "VmbErrorNotSupported",
36        VmbErrorIncomplete => "VmbErrorIncomplete",
37        VmbErrorIO => "VmbErrorIO",
38        VmbErrorValidValueSetNotPresent => "VmbErrorValidValueSetNotPresent",
39        VmbErrorGenTLUnspecified => "VmbErrorGenTLUnspecified",
40        VmbErrorUnspecified => "VmbErrorUnspecified",
41        VmbErrorBusy => "VmbErrorBusy",
42        VmbErrorNoData => "VmbErrorNoData",
43        VmbErrorParsingChunkData => "VmbErrorParsingChunkData",
44        VmbErrorInUse => "VmbErrorInUse",
45        VmbErrorUnknown => "VmbErrorUnknown",
46        VmbErrorXml => "VmbErrorXml",
47        VmbErrorNotAvailable => "VmbErrorNotAvailable",
48        VmbErrorNotInitialized => "VmbErrorNotInitialized",
49        VmbErrorInvalidAddress => "VmbErrorInvalidAddress",
50        VmbErrorAlready => "VmbErrorAlready",
51        VmbErrorNoChunkData => "VmbErrorNoChunkData",
52        VmbErrorUserCallbackException => "VmbErrorUserCallbackException",
53        VmbErrorFeaturesUnavailable => "VmbErrorFeaturesUnavailable",
54        VmbErrorTLNotFound => "VmbErrorTLNotFound",
55        VmbErrorAmbiguous => "VmbErrorAmbiguous",
56        VmbErrorRetriesExceeded => "VmbErrorRetriesExceeded",
57        VmbErrorInsufficientBufferCount => "VmbErrorInsufficientBufferCount",
58        VmbErrorCustom => "VmbErrorCustom",
59        _ => "unknown error",
60    }
61}
62
63#[derive(thiserror::Error, Debug)]
64#[error("Vimba X error {code}: {msg}")]
65pub struct VimbaError {
66    pub code: i32,
67    pub msg: &'static str,
68}
69
70impl From<i32> for VimbaError {
71    fn from(code: i32) -> VimbaError {
72        VimbaError {
73            code,
74            msg: err_str(code),
75        }
76    }
77}
78
79#[derive(thiserror::Error, Debug)]
80pub enum Error {
81    #[error("Loading library at {vimbac_path}")]
82    LibLoading {
83        source: libloading::Error,
84        vimbac_path: std::path::PathBuf,
85    },
86    #[error("{source}")]
87    Vimba {
88        #[from]
89        source: VimbaError,
90    },
91    #[error("{source}")]
92    NulError {
93        #[from]
94        source: std::ffi::NulError,
95    },
96    #[error("{source}")]
97    Utf8Error {
98        #[from]
99        source: std::str::Utf8Error,
100    },
101    #[error("unknown pixel format {fmt}")]
102    UnknownPixelFormat { fmt: String },
103    #[error("unknown pixel format code 0x{code:X}")]
104    UnknownPixelFormatCode { code: u32 },
105    #[error("invalid call")]
106    InvalidCall {},
107    #[cfg(target_os = "windows")]
108    #[error("{source}")]
109    Windows {
110        #[from]
111        source: windows::core::Error,
112    },
113}
114
115pub type Result<T> = std::result::Result<T, Error>;
116
117fn vimba_err(err: i32) -> std::result::Result<(), VimbaError> {
118    if err == VmbErrorType::VmbErrorSuccess {
119        Ok(())
120    } else {
121        Err(VimbaError::from(err))
122    }
123}
124
125macro_rules! vimba_call_no_err {
126    ($expr: expr_2021) => {{
127        tracing::debug!("calling: {} {}:{}", stringify!($expr), file!(), line!());
128        unsafe { $expr }
129    }};
130}
131
132macro_rules! vimba_call {
133    ($expr: expr_2021) => {{
134        let errcode = vimba_call_no_err!($expr);
135        tracing::debug!("  errcode: {}", errcode);
136
137        vimba_err(errcode)
138    }};
139}
140
141pub struct VimbaLibrary {
142    pub vimba_lib: vmbc_sys::VimbaC,
143    started: bool,
144}
145
146impl VimbaLibrary {
147    pub fn new() -> std::result::Result<Self, Error> {
148        let vimbac_path = match std::env::var_os("VIMBA_X_HOME") {
149            Some(vimba_x_home) => std::path::PathBuf::from(vimba_x_home)
150                .join("api")
151                .join("lib"),
152            None => {
153                #[cfg(target_os = "windows")]
154                let vmbc_path = {
155                    // Tell Windows to add this directory to DLL search path.
156                    let dll_path =
157                        windows::core::s!(r#"C:\Program Files\Allied Vision\Vimba X\bin"#);
158                    unsafe { windows::Win32::System::LibraryLoader::SetDllDirectoryA(dll_path) }?;
159                    // Now we directly open this DLL, which should now be on the search path.
160                    "VmbC.dll"
161                };
162
163                // Important note when updating this: also update the files in
164                // `_packaging/ubuntu-*-installer-zip-readme.txt` and
165                // `_packaging/setup-ubuntu-*-vimba.sh` to match this.
166                #[cfg(target_os = "linux")]
167                let vmbc_path = "/opt/VimbaX_2024-1/api/lib/libVmbC.so";
168
169                #[cfg(target_os = "macos")]
170                let vmbc_path = "/Library/Frameworks/VmbC.framework/Versions/A/VmbC";
171                std::path::PathBuf::from(vmbc_path)
172            }
173        };
174
175        Self::from_dynamic_lib_path(vimbac_path)
176    }
177
178    /// This is unsafe because really you should drop [VimbaLibrary] rather than
179    /// this. If you are using this, it cannot be guaranteed that VmbShutdown
180    /// will not be called again.
181    ///
182    /// # Safety
183    ///
184    /// The caller must ensure that `VmbShutdown` is not called again (for
185    /// example via dropping [VimbaLibrary]) after this call.
186    pub unsafe fn shutdown(&self) {
187        vimba_call_no_err!(self.vimba_lib.VmbShutdown());
188    }
189
190    pub fn from_dynamic_lib_path<P: AsRef<std::path::Path>>(
191        vimbac_path: P,
192    ) -> std::result::Result<Self, Error> {
193        let vimba_lib = match unsafe { vmbc_sys::VimbaC::new(vimbac_path.as_ref()) } {
194            Ok(vimba_lib) => vimba_lib,
195            Err(source) => {
196                let vimbac_path = vimbac_path.as_ref().to_path_buf();
197                return Err(Error::LibLoading {
198                    source,
199                    vimbac_path,
200                });
201            }
202        };
203
204        vimba_call!(vimba_lib.VmbStartup(std::ptr::null()))?;
205        Ok(VimbaLibrary {
206            vimba_lib,
207            started: true,
208        })
209    }
210
211    pub fn n_cameras(&self) -> Result<usize> {
212        let mut n_count = 0;
213        vimba_call!(
214            self.vimba_lib
215                .VmbCamerasList(std::ptr::null_mut(), 0, &mut n_count, 0)
216        )?;
217        Ok(n_count as usize)
218    }
219
220    pub fn camera_info(&self, n_count: usize) -> Result<Vec<CameraInfo>> {
221        let mut cameras: Vec<VmbCameraInfo_t> = vec![
222            VmbCameraInfo_t {
223                cameraIdString: std::ptr::null_mut(),
224                cameraIdExtended: std::ptr::null_mut(),
225                cameraName: std::ptr::null_mut(),
226                modelName: std::ptr::null_mut(),
227                serialString: std::ptr::null_mut(),
228                transportLayerHandle: std::ptr::null_mut(),
229                interfaceHandle: std::ptr::null_mut(),
230                localDeviceHandle: std::ptr::null_mut(),
231                streamHandles: std::ptr::null_mut(),
232                streamCount: 0,
233                permittedAccess: 0,
234            };
235            n_count
236        ];
237
238        let mut n_found_count = 0;
239        vimba_call!(self.vimba_lib.VmbCamerasList(
240            cameras[..].as_mut_ptr(),
241            n_count.try_into().unwrap(),
242            &mut n_found_count,
243            std::mem::size_of::<VmbCameraInfo_t>().try_into().unwrap()
244        ))?;
245
246        let result = cameras
247            .into_iter()
248            .map(|ci| CameraInfo {
249                camera_id_string: unsafe { std::ffi::CStr::from_ptr(ci.cameraIdString).to_str() }
250                    .unwrap()
251                    .to_string(),
252                camera_id_extended: unsafe {
253                    std::ffi::CStr::from_ptr(ci.cameraIdExtended).to_str()
254                }
255                .unwrap()
256                .to_string(),
257                camera_name: unsafe { std::ffi::CStr::from_ptr(ci.cameraName).to_str() }
258                    .unwrap()
259                    .to_string(),
260                model_name: unsafe { std::ffi::CStr::from_ptr(ci.modelName).to_str() }
261                    .unwrap()
262                    .to_string(),
263                serial_string: unsafe { std::ffi::CStr::from_ptr(ci.serialString).to_str() }
264                    .unwrap()
265                    .to_string(),
266                permitted_access: AccessMode::new(ci.permittedAccess),
267            })
268            .collect();
269        Ok(result)
270    }
271}
272
273impl Drop for VimbaLibrary {
274    fn drop(&mut self) {
275        if self.started {
276            vimba_call_no_err!(self.vimba_lib.VmbShutdown());
277            self.started = false;
278        }
279    }
280}
281
282pub struct VersionInfo {
283    pub major: u32,
284    pub minor: u32,
285    pub patch: u32,
286}
287
288impl VersionInfo {
289    pub fn new(vimba_c: &vmbc_sys::VimbaC) -> Result<Self> {
290        let mut version_info = VmbVersionInfo_t {
291            major: 0,
292            minor: 0,
293            patch: 0,
294        };
295        vimba_call!(vimba_c.VmbVersionQuery(
296            &mut version_info,
297            std::mem::size_of::<VmbVersionInfo_t>() as u32
298        ))?;
299        Ok(Self {
300            major: version_info.major,
301            minor: version_info.minor,
302            patch: version_info.patch,
303        })
304    }
305}
306
307#[derive(Debug)]
308pub struct AccessMode {
309    code: u32,
310}
311
312impl AccessMode {
313    pub fn new(code: u32) -> Self {
314        Self { code }
315    }
316    pub fn as_u32(&self) -> u32 {
317        self.code
318    }
319}
320
321pub mod access_mode {
322    use vmbc_sys::VmbAccessModeType::*;
323    pub const FULL: crate::AccessMode = crate::AccessMode {
324        code: VmbAccessModeFull,
325    };
326}
327
328#[derive(Debug)]
329pub struct CameraInfo {
330    pub camera_id_string: String,
331    pub camera_id_extended: String,
332    pub camera_name: String,
333    pub model_name: String,
334    pub serial_string: String,
335    pub permitted_access: AccessMode,
336}
337
338pub struct Camera<'lib> {
339    handle: VmbHandle_t,
340    is_open: bool,
341    vimba_lib: &'lib vmbc_sys::VimbaC,
342}
343
344unsafe impl<'lib> Send for Camera<'lib> {}
345
346fn _test_camera_is_send() {
347    // Compile-time test to ensure Camera implements Send trait.
348    fn implements<T: Send>() {}
349    implements::<Camera>();
350}
351
352impl<'lib> std::fmt::Debug for Camera<'lib> {
353    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
354        write!(fmt, "Camera {{")?;
355        write!(fmt, " self.handle {:p},", self.handle)?;
356        write!(fmt, "}}")?;
357        Ok(())
358    }
359}
360
361impl<'lib> Camera<'lib> {
362    pub fn open(
363        camera_id: &str,
364        access_mode: AccessMode,
365        vimba_lib: &'lib vmbc_sys::VimbaC,
366    ) -> Result<Self> {
367        let data = std::ffi::CString::new(camera_id)?;
368        let mut handle = std::mem::MaybeUninit::<VmbHandle_t>::uninit();
369        vimba_call!(vimba_lib.VmbCameraOpen(
370            data.as_ptr(),
371            access_mode.as_u32(),
372            handle.as_mut_ptr()
373        ))?;
374        let handle = unsafe { handle.assume_init() };
375        let result = Self {
376            handle,
377            is_open: true,
378            vimba_lib,
379        };
380        tracing::debug!("opening {:?}", result);
381        Ok(result)
382    }
383
384    pub fn close(mut self) -> Result<()> {
385        if self.is_open {
386            vimba_call!(self.vimba_lib.VmbCameraClose(self.handle))?;
387        }
388        self.is_open = false; // prevent closing again on drop
389        Ok(())
390    }
391
392    pub fn handle(&self) -> VmbHandle_t {
393        self.handle
394    }
395
396    pub fn feature_enum(&self, feature_name: &str) -> Result<&'static str> {
397        let mut result: *const std::os::raw::c_char = std::ptr::null_mut();
398        let data = std::ffi::CString::new(feature_name)?;
399        vimba_call!(
400            self.vimba_lib
401                .VmbFeatureEnumGet(self.handle, data.as_ptr(), &mut result)
402        )?;
403        Ok(unsafe { std::ffi::CStr::from_ptr(result).to_str()? })
404    }
405
406    pub fn feature_enum_range_query(&self, feature_name: &str) -> Result<Vec<String>> {
407        let name = std::ffi::CString::new(feature_name)?;
408        let mut num_filled = 0;
409        // initial query: get size of array
410        vimba_call!(self.vimba_lib.VmbFeatureEnumRangeQuery(
411            self.handle,
412            name.as_ptr(),
413            std::ptr::null_mut(),
414            0,
415            &mut num_filled,
416        ))?;
417
418        let mut p_name_array = vec![std::ptr::null(); num_filled.try_into().unwrap()];
419
420        let mut num_final = 0;
421        vimba_call!(self.vimba_lib.VmbFeatureEnumRangeQuery(
422            self.handle,
423            name.as_ptr(),
424            p_name_array.as_mut_ptr(),
425            num_filled,
426            &mut num_final,
427        ))?;
428
429        (0..num_final as usize)
430            .map(|i| {
431                let c_str_ptr = p_name_array[i];
432                let value = unsafe { std::ffi::CStr::from_ptr(c_str_ptr) }
433                    .to_str()?
434                    .to_string();
435                Ok(value)
436            })
437            .collect()
438    }
439
440    pub fn feature_enum_set(&self, feature_name: &str, value: &str) -> Result<()> {
441        let value_c = std::ffi::CString::new(value)?;
442        let name = std::ffi::CString::new(feature_name)?;
443        vimba_call!(self.vimba_lib.VmbFeatureEnumSet(
444            self.handle,
445            name.as_ptr(),
446            value_c.as_ptr()
447        ))?;
448        Ok(())
449    }
450
451    // pub fn features_list(&self) -> Result<Vec<FeatureInfo>> {
452    //     let mut num_found = 0;
453    //     vimba_call!(self.vimba_lib.VmbFeaturesList(
454    //         self.handle,
455    //         std::ptr::null_mut(),
456    //         0,
457    //         &mut num_found,
458    //         std::mem::size_of::<VmbFeatureInfo_t>().try_into().unwrap()
459    //     ))?;
460
461    //     let mut feature_infos =
462    //         vec![std::ptr::null_mut() as *mut VmbFeatureInfo_t; num_found.try_into().unwrap()];
463    //     let mut num_filled = 0;
464    //     vimba_call!(self.vimba_lib.VmbFeaturesList(
465    //         self.handle,
466    //         *feature_infos.as_mut_ptr(),
467    //         num_found,
468    //         &mut num_filled,
469    //         std::mem::size_of::<VmbFeatureInfo_t>().try_into().unwrap()
470    //     ))?;
471
472    //     let result = feature_infos.into_iter().map(From::from).collect();
473    //     Ok(result)
474    // }
475
476    /// Query the access permissions of feature with `name`.
477    ///
478    /// The return value is (is_readable, is_writeable).
479    pub fn feature_access_query(&self, name: &str) -> Result<(bool, bool)> {
480        let mut is_readable = 0;
481        let mut is_writeable = 0;
482        vimba_call!(self.vimba_lib.VmbFeatureAccessQuery(
483            self.handle,
484            name.as_ptr() as _,
485            &mut is_readable,
486            &mut is_writeable,
487        ))?;
488
489        Ok((is_readable != 0, is_writeable != 0))
490    }
491
492    // pub fn feature_string(&self, feature_name: &str) -> Result<&str> {
493    //     let mut result: *const std::os::raw::c_char = std::ptr::null_mut();
494    //     let data = std::ffi::CString::new(feature_name)?;
495    //     vimba_call!(self.vimba_lib.VmbFeatureStringGet(self.handle, data.as_ptr(), &mut result))?;
496    //     Ok(unsafe { std::ffi::CStr::from_ptr(result).to_str()? })
497    // }
498
499    pub fn feature_string_set(&self, feature_name: &str, value: &str) -> Result<()> {
500        let value_c = std::ffi::CString::new(value)?;
501        let name = std::ffi::CString::new(feature_name)?;
502        vimba_call!(self.vimba_lib.VmbFeatureStringSet(
503            self.handle,
504            name.as_ptr(),
505            value_c.as_ptr()
506        ))?;
507        Ok(())
508    }
509
510    pub fn feature_int(&self, feature_name: &str) -> Result<i64> {
511        let mut result = 0;
512        let data = std::ffi::CString::new(feature_name)?;
513        vimba_call!(
514            self.vimba_lib
515                .VmbFeatureIntGet(self.handle, data.as_ptr(), &mut result)
516        )?;
517        Ok(result)
518    }
519
520    pub fn feature_int_set(&self, feature_name: &str, value: i64) -> Result<()> {
521        let data = std::ffi::CString::new(feature_name)?;
522        vimba_call!(
523            self.vimba_lib
524                .VmbFeatureIntSet(self.handle, data.as_ptr(), value)
525        )?;
526        Ok(())
527    }
528
529    pub fn feature_float(&self, feature_name: &str) -> Result<f64> {
530        let mut result = 0.0;
531        let data = std::ffi::CString::new(feature_name)?;
532        vimba_call!(
533            self.vimba_lib
534                .VmbFeatureFloatGet(self.handle, data.as_ptr(), &mut result)
535        )?;
536        Ok(result)
537    }
538
539    pub fn feature_float_set(&self, feature_name: &str, value: f64) -> Result<()> {
540        let data = std::ffi::CString::new(feature_name)?;
541        vimba_call!(
542            self.vimba_lib
543                .VmbFeatureFloatSet(self.handle, data.as_ptr(), value)
544        )?;
545        Ok(())
546    }
547
548    pub fn feature_float_range_query(&self, feature_name: &str) -> Result<(f64, f64)> {
549        let mut min = 0.0;
550        let mut max = 0.0;
551        let data = std::ffi::CString::new(feature_name)?;
552        vimba_call!(self.vimba_lib.VmbFeatureFloatRangeQuery(
553            self.handle,
554            data.as_ptr(),
555            &mut min,
556            &mut max
557        ))?;
558        Ok((min, max))
559    }
560
561    pub fn feature_boolean(&self, feature_name: &str) -> Result<bool> {
562        let mut result = 0;
563        let data = std::ffi::CString::new(feature_name)?;
564        vimba_call!(
565            self.vimba_lib
566                .VmbFeatureBoolGet(self.handle, data.as_ptr(), &mut result)
567        )?;
568        Ok(result != 0)
569    }
570    pub fn feature_boolean_set(&self, feature_name: &str, value: bool) -> Result<()> {
571        let value_u8 = if value { 1 } else { 0 };
572        let data = std::ffi::CString::new(feature_name)?;
573        vimba_call!(
574            self.vimba_lib
575                .VmbFeatureBoolSet(self.handle, data.as_ptr(), value_u8)
576        )?;
577        Ok(())
578    }
579
580    pub fn command_run(&self, command_name: &str) -> Result<()> {
581        tracing::debug!("camera {:?} command_run {}", self, command_name);
582        let data = std::ffi::CString::new(command_name)?;
583        vimba_call!(
584            self.vimba_lib
585                .VmbFeatureCommandRun(self.handle, data.as_ptr())
586        )?;
587        Ok(())
588    }
589
590    pub fn pixel_format(&self) -> Result<formats::pixel_format::PixFmt> {
591        let pixel_format = self.feature_enum("PixelFormat")?;
592        str_to_pixel_format(pixel_format)
593    }
594
595    pub fn allocate_buffer(&self) -> Result<Vec<u8>> {
596        let payload_size = self.feature_int("PayloadSize")?;
597        Ok(vec![0u8; payload_size.try_into().unwrap()])
598    }
599
600    pub fn frame_announce(&self, frame: &mut Frame) -> Result<()> {
601        if frame.already_announced {
602            return Err(Error::InvalidCall {});
603        }
604
605        tracing::debug!("camera {:?} announcing frame {:?}", self, frame);
606
607        vimba_call!(self.vimba_lib.VmbFrameAnnounce(
608            self.handle,
609            &*frame.frame,
610            std::mem::size_of::<VmbFrame_t>().try_into().unwrap()
611        ))?;
612
613        frame.already_announced = true;
614        Ok(())
615    }
616
617    pub fn frame_revoke(&self, frame: &mut Frame) -> Result<()> {
618        tracing::debug!("camera {:?} revoking frame {:?}", self, frame);
619        vimba_call!(self.vimba_lib.VmbFrameRevoke(self.handle, &*frame.frame,))?;
620        frame.already_announced = false;
621        Ok(())
622    }
623
624    pub fn capture_start(&self) -> Result<()> {
625        tracing::debug!("camera {:?} capture start", self);
626        vimba_call!(self.vimba_lib.VmbCaptureStart(self.handle))?;
627        Ok(())
628    }
629
630    pub fn capture_end(&self) -> Result<()> {
631        vimba_call!(self.vimba_lib.VmbCaptureEnd(self.handle))?;
632        Ok(())
633    }
634
635    pub fn capture_frame_queue(&self, frame: &mut Frame) -> Result<()> {
636        tracing::debug!("camera {:?} queueing frame {:?}", self, frame);
637        vimba_call!(
638            self.vimba_lib
639                .VmbCaptureFrameQueue(self.handle, &*frame.frame, None)
640        )?;
641        Ok(())
642    }
643    pub fn capture_frame_queue_with_callback(
644        &self,
645        frame: &mut Frame,
646        callback: VmbFrameCallback,
647    ) -> Result<()> {
648        tracing::debug!("camera {:?} queueing frame {:?}", self, frame);
649        vimba_call!(
650            self.vimba_lib
651                .VmbCaptureFrameQueue(self.handle, &*frame.frame, callback)
652        )?;
653        Ok(())
654    }
655
656    pub fn capture_queue_flush(&self) -> Result<()> {
657        vimba_call!(self.vimba_lib.VmbCaptureQueueFlush(self.handle))?;
658        Ok(())
659    }
660
661    pub fn capture_frame_wait(&self, frame: &mut Frame, timeout: u32) -> Result<()> {
662        tracing::debug!("camera {:?} waiting for frame {:?}", self, frame);
663        vimba_call!(
664            self.vimba_lib
665                .VmbCaptureFrameWait(self.handle, &*frame.frame, timeout)
666        )?;
667        Ok(())
668    }
669
670    pub fn camera_settings_save<P: AsRef<std::path::Path>>(
671        &self,
672        out_path: P,
673        p_settings: &VmbFeaturePersistSettings_t,
674    ) -> Result<()> {
675        let mut buf = path_to_bytes(out_path);
676        buf.push(0);
677        let sz = std::mem::size_of::<VmbFeaturePersistSettings_t>();
678        let sz = sz.try_into().unwrap(); // convert to u32 from usize
679        vimba_call!(self.vimba_lib.VmbSettingsSave(
680            self.handle,
681            buf.as_ptr() as *const i8,
682            p_settings as *const _,
683            sz
684        ))?;
685        Ok(())
686    }
687
688    pub fn camera_settings_load<P: AsRef<std::path::Path>>(
689        &self,
690        in_path: P,
691        p_settings: &VmbFeaturePersistSettings_t,
692    ) -> Result<()> {
693        let mut buf = path_to_bytes(in_path);
694        buf.push(0);
695        let sz = std::mem::size_of::<VmbFeaturePersistSettings_t>();
696        let sz = sz.try_into().unwrap(); // convert to u32 from usize
697        vimba_call!(self.vimba_lib.VmbSettingsLoad(
698            self.handle,
699            buf.as_ptr() as *const i8,
700            p_settings as *const _,
701            sz
702        ))?;
703        Ok(())
704    }
705}
706
707impl<'lib> Drop for Camera<'lib> {
708    fn drop(&mut self) {
709        if self.is_open {
710            vimba_call!(self.vimba_lib.VmbCameraClose(self.handle)).unwrap();
711            self.is_open = false;
712        }
713    }
714}
715
716pub fn default_feature_persist_settings() -> VmbFeaturePersistSettings_t {
717    // These values are the defaults in vmbpy-1.0.4 from Vimba X in the
718    // save_settings() and load_settings() function signatures with the
719    // exception of maxIterations and loggingLevel, which are the values that
720    // Vimba X viewer seems to use.
721    VmbFeaturePersistSettings_t {
722        persistType: vmbc_sys::VmbFeaturePersistType::VmbFeaturePersistStreamable,
723        modulePersistFlags: vmbc_sys::VmbModulePersistFlagsType::VmbModulePersistFlagsNone,
724        maxIterations: 10,
725        loggingLevel: 4,
726    }
727}
728
729// TODO: should we use `std::pin::Pin` to ensure that `buffer` is not moved?
730pub struct Frame {
731    buffer: Vec<u8>,
732    // the address of `frame` is used as a key by Vimba to remember locations, so it must remain fixed.
733    // `frame` contains a pointer to `buffer`
734    frame: Pin<Box<VmbFrame_t>>,
735    already_announced: bool,
736}
737
738unsafe impl Send for Frame {}
739
740fn _test_frame_is_send() {
741    // Compile-time test to ensure Frame implements Send trait.
742    fn implements<T: Send>() {}
743    implements::<Frame>();
744}
745
746impl std::fmt::Debug for Frame {
747    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
748        write!(fmt, "Frame {{")?;
749        write!(
750            fmt,
751            " frame as *const VmbFrame_t {:p},",
752            &*self.frame as *const VmbFrame_t
753        )?;
754        write!(fmt, " buffer.as_ptr() {:p},", self.buffer.as_ptr())?;
755        write!(fmt, " buffer.len() {},", self.buffer.len())?;
756        write!(fmt, " frame.buffer {:p},", self.frame.buffer)?;
757        write!(fmt, " frame.bufferSize {},", self.frame.bufferSize)?;
758        write!(fmt, " already_announced {:?},", self.already_announced)?;
759        write!(fmt, "}}")?;
760        Ok(())
761    }
762}
763
764impl Frame {
765    pub fn new(mut buffer: Vec<u8>) -> Self {
766        let frame = Box::pin(VmbFrame_t {
767            buffer: buffer.as_mut_ptr() as _,
768            bufferSize: buffer.len().try_into().unwrap(),
769            context: [std::ptr::null_mut(); 4],
770            receiveStatus: 0,
771            frameID: 0,
772            timestamp: 0,
773            imageData: std::ptr::null_mut(),
774            receiveFlags: 0,
775            pixelFormat: 0,
776            width: 0,
777            height: 0,
778            offsetX: 0,
779            offsetY: 0,
780            payloadType: vmbc_sys::VmbPayloadType::VmbPayloadTypeUnknown,
781            chunkDataPresent: 0, // vmbc_sys::VmbBoolVal::VmbBoolFalse,
782        });
783
784        Self {
785            buffer,
786            frame,
787            already_announced: false,
788        }
789    }
790    #[inline]
791    pub fn is_complete(&self) -> bool {
792        self.frame.receiveStatus == VmbFrameStatusType::VmbFrameStatusComplete
793    }
794    #[inline]
795    pub fn width(&self) -> u32 {
796        self.frame.width
797    }
798    #[inline]
799    pub fn height(&self) -> u32 {
800        self.frame.height
801    }
802    #[inline]
803    pub fn buffer_size(&self) -> usize {
804        self.frame.bufferSize.try_into().unwrap()
805    }
806    #[inline]
807    pub fn buffer(&self) -> &[u8] {
808        &self.buffer[..self.buffer_size()]
809    }
810    #[inline]
811    pub fn frame_id(&self) -> u64 {
812        self.frame.frameID
813    }
814    #[inline]
815    pub fn timestamp(&self) -> u64 {
816        self.frame.timestamp
817    }
818    #[inline]
819    pub fn pixel_format(&self) -> Result<formats::PixFmt> {
820        pixel_format_code(self.frame.pixelFormat)
821    }
822}
823
824pub fn pixel_format_code(code: u32) -> Result<formats::PixFmt> {
825    use formats::PixFmt::*;
826    use vmbc_sys::VmbPixelFormatType::*;
827    #[expect(non_upper_case_globals)]
828    let fmt = match code {
829        VmbPixelFormatMono8 => Mono8,
830        VmbPixelFormatBayerGR8 => BayerGR8,
831        VmbPixelFormatBayerRG8 => BayerRG8,
832        VmbPixelFormatBayerGB8 => BayerGB8,
833        VmbPixelFormatBayerBG8 => BayerBG8,
834        VmbPixelFormatRgb8 => RGB8,
835        // VmbPixelFormatMono10 => Mono10,
836        // VmbPixelFormatMono10p => Mono10p,
837        // VmbPixelFormatMono12 => Mono12,
838        // VmbPixelFormatMono12p => Mono12p,
839        // VmbPixelFormatMono16 => Mono16,
840        _code_signed => {
841            return Err(Error::UnknownPixelFormatCode { code });
842        }
843    };
844    Ok(fmt)
845}
846
847pub fn str_to_pixel_format(pixel_format: &str) -> Result<formats::pixel_format::PixFmt> {
848    use formats::pixel_format::PixFmt::*;
849    Ok(match pixel_format {
850        "Mono8" => Mono8,
851        "RGB8" => RGB8,
852        "BayerRG8" => BayerRG8,
853        // "Mono10" => Mono10,
854        // "Mono10p" => Mono10p,
855        // "Mono12" => Mono12,
856        // "Mono12p" => Mono12p,
857        // "Mono16" => Mono16,
858        fmt => {
859            return Err(Error::UnknownPixelFormat {
860                fmt: fmt.to_string(),
861            });
862        }
863    })
864}
865
866pub fn pixel_format_to_str(pixfmt: formats::pixel_format::PixFmt) -> Result<&'static str> {
867    use formats::pixel_format::PixFmt::*;
868    Ok(match pixfmt {
869        Mono8 => "Mono8",
870        RGB8 => "RGB8",
871        // Mono10 => "Mono10",
872        // Mono10p => "Mono10p",
873        // Mono12 => "Mono12",
874        // Mono12p => "Mono12p",
875        // Mono16 => "Mono16",
876        _ => {
877            return Err(Error::UnknownPixelFormat {
878                fmt: format!("pixfmt {:?}", pixfmt),
879            });
880        }
881    })
882}
883
884// #[derive(Debug, Clone, PartialEq)]
885// pub struct FeatureInfo {
886//     pub name: String,
887//     pub data_type: DataType,
888//     pub access_flags: AccessFlags,
889//     // pub category: String,
890//     pub display_name: String,
891//     // pub polling_time: u32,
892//     // pub unit: String,
893//     // pub representation: String,
894//     // pub visibility: Visibility,
895//     // pub tooltip: String,
896//     // pub description: String,
897//     // pub sfnc_namespace: String,
898//     // pub is_streamable: bool,
899//     // pub has_affected_features: bool,
900//     // pub has_selected_features: bool,
901// }
902
903// #[derive(Debug, Clone, PartialEq)]
904// pub enum DataType {
905//     Unknown,
906//     Int,
907//     Float,
908//     Enum,
909//     String,
910//     Bool,
911//     Command,
912//     Raw,
913//     None,
914// }
915
916// impl DataType {
917//     pub fn new(orig: vmbc_sys::VmbFeatureData_t) -> Self {
918//         use vmbc_sys::VmbFeatureDataType::*;
919//         use DataType::*;
920//         #[expect(non_upper_case_globals)]
921//         match orig as i32 {
922//             VmbFeatureDataUnknown => Unknown,
923//             VmbFeatureDataInt => Int,
924//             VmbFeatureDataFloat => Float,
925//             VmbFeatureDataEnum => Enum,
926//             VmbFeatureDataString => String,
927//             VmbFeatureDataBool => Bool,
928//             VmbFeatureDataCommand => Command,
929//             VmbFeatureDataRaw => Raw,
930//             VmbFeatureDataNone => None,
931//             o => {
932//                 panic!("unknown data type {}", o);
933//             }
934//         }
935//     }
936// }
937
938// #[derive(Debug, Clone, PartialEq)]
939// pub struct AccessFlags {
940//     flags: vmbc_sys::VmbFeatureFlags_t,
941// }
942
943// impl AccessFlags {
944//     pub fn new(orig: vmbc_sys::VmbFeatureFlags_t) -> Self {
945//         Self { flags: orig }
946//     }
947// }
948
949// impl From<*mut VmbFeatureInfo_t> for FeatureInfo {
950//     fn from(orig: *mut VmbFeatureInfo_t) -> Self {
951//         let name = unsafe { std::ffi::CStr::from_ptr((*orig).name).to_str() }
952//             .unwrap()
953//             .to_string();
954//         let display_name = unsafe { std::ffi::CStr::from_ptr((*orig).displayName).to_str() }
955//             .unwrap()
956//             .to_string();
957
958//         let data_type = DataType::new(unsafe { (*orig).featureDataType });
959//         let access_flags = AccessFlags::new(unsafe { (*orig).featureFlags });
960
961//         Self {
962//             name,
963//             data_type,
964//             access_flags,
965//             display_name,
966//         }
967//     }
968// }
969
970/// Convert path to bytes
971///
972/// From https://stackoverflow.com/a/57667836/1633026
973#[cfg(unix)]
974fn path_to_bytes<P: AsRef<std::path::Path>>(path: P) -> Vec<u8> {
975    use std::os::unix::ffi::OsStrExt;
976    path.as_ref().as_os_str().as_bytes().to_vec()
977}
978
979/// Convert path to bytes
980///
981/// From https://stackoverflow.com/a/57667836/1633026
982#[cfg(not(unix))]
983fn path_to_bytes<P: AsRef<std::path::Path>>(path: P) -> Vec<u8> {
984    // On Windows, could use std::os::windows::ffi::OsStrExt to encode_wide(),
985    // but you end up with a Vec<u16> instead of a Vec<u8>, so that doesn't
986    // really help. This is probably wrong for paths with non ascii characters,
987    // but the Vimba docs don't specify what encoding is used, so it is hard to
988    // know the right thing to do.
989    path.as_ref().to_string_lossy().to_string().into_bytes()
990}