Skip to main content

ci2_vimba/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::{
5    convert::TryInto,
6    sync::{
7        Arc, Mutex, OnceLock,
8        atomic::{AtomicBool, Ordering},
9    },
10};
11use tracing::{error, warn};
12
13use machine_vision_formats as formats;
14
15use ci2::{AcquisitionMode, AutoMode, DynamicFrameWithInfo, HostTimingInfo, TriggerMode};
16use formats::PixFmt;
17
18use std::sync::mpsc::{Receiver, SyncSender};
19use strand_dynamic_frame::DynamicFrameOwned;
20
21// Number of frames to allocate for the Vimba driver.
22const N_BUFFER_FRAMES: usize = 10;
23// Number of slots to allocate purely within rust.
24const N_CHANNEL_FRAMES: usize = 10;
25
26struct FrameSender {
27    handle: CamHandle,
28    tx: SyncSender<std::result::Result<InvalidHostFramenumber, ci2::Error>>,
29}
30
31struct CamHandle {
32    inner: vmbc_sys::VmbHandle_t,
33}
34
35unsafe impl Sync for CamHandle {}
36unsafe impl Send for CamHandle {}
37
38/// The Vimba SDK shared library, loaded once by [new_module]. Loading the SDK
39/// can fail (e.g. it is not installed); that fallible step is handled in
40/// [new_module] so that the rest of the code can access the library infallibly
41/// via [vimba_lib].
42static VIMBA_LIB: OnceLock<vimba::VimbaLibrary> = OnceLock::new();
43static IS_DONE: AtomicBool = AtomicBool::new(false);
44static SENDERS: Mutex<Vec<FrameSender>> = Mutex::new(Vec::new());
45
46/// Access the Vimba library that [new_module] loaded.
47///
48/// Panics if called before a successful [new_module], which the code guarantees:
49/// a [WrappedModule] (the only entry point to this backend) can only be obtained
50/// from [new_module], which loads the library first.
51fn vimba_lib() -> &'static vimba::VimbaLibrary {
52    VIMBA_LIB
53        .get()
54        .expect("Vimba library accessed before new_module() loaded it")
55}
56
57/// convert vimba::Error to ci2::Error
58fn ve2ce(orig: vimba::Error) -> ci2::Error {
59    // If `orig` contains a backtrace, the Debug reprepresentation has it, so it
60    // will get included as a string to the error here. TODO: `anyhow::Error`
61    // should use the backtrace in `orig` (without converting it to a String).
62    ci2::Error::from(anyhow::anyhow!("vimba::Error: {orig:?}"))
63}
64
65fn callback_rust(
66    camera_handle: vmbc_sys::VmbHandle_t,
67    frame: *mut vmbc_sys::VmbFrame_t,
68) -> ci2::Result<()> {
69    let now = chrono::Utc::now(); // earliest possible timestamp
70    let frame_status = unsafe { (*frame).receiveStatus };
71    if !IS_DONE.load(Ordering::Relaxed) {
72        // Copy all data from Vimba.
73
74        let msg = if frame_status == vmbc_sys::VmbFrameStatusType::VmbFrameStatusComplete {
75            // Make reference to image buffer.
76            let buf_ref = unsafe {
77                let buf_ref1 = (*frame).buffer;
78                let buf_len = (*frame).bufferSize as usize;
79                std::slice::from_raw_parts(buf_ref1 as *const u8, buf_len)
80            };
81            // Copy image buffer.
82            let image_data = buf_ref.to_vec(); // makes copy
83
84            // Copy other pieces of information.
85            let code = unsafe { (*frame).pixelFormat };
86
87            let flags = unsafe { (*frame).receiveFlags };
88            let frame_id = if flags & vmbc_sys::VmbFrameFlagsType::VmbFrameFlagsFrameID.0 != 0 {
89                unsafe { (*frame).frameID }
90            } else {
91                eprintln!("no frame number data in frame");
92                0
93            };
94
95            let device_timestamp =
96                if flags & vmbc_sys::VmbFrameFlagsType::VmbFrameFlagsTimestamp.0 != 0 {
97                    unsafe { (*frame).timestamp }
98                } else {
99                    eprintln!("no timestamp data in frame");
100                    0
101                };
102
103            let pixel_format = vimba::pixel_format_code(code).map_vimba_err()?;
104
105            {
106                let extra = Box::new(ci2_vimba_types::VimbaExtra {
107                    frame_id,
108                    device_timestamp,
109                });
110
111                let width = unsafe { (*frame).width };
112                let height = unsafe { (*frame).height };
113
114                // Compute minimum stride.
115                let min_stride = width as usize * pixel_format.bits_per_pixel() as usize / 8;
116                debug_assert!(min_stride * height as usize == image_data.len());
117                let image = Arc::new(
118                    DynamicFrameOwned::from_buf(
119                        width,
120                        height,
121                        min_stride,
122                        image_data,
123                        pixel_format,
124                    )
125                    .unwrap(),
126                );
127
128                Ok(InvalidHostFramenumber(DynamicFrameWithInfo {
129                    image,
130                    host_timing: HostTimingInfo {
131                        fno: 0, // will be fixed later
132                        datetime: now,
133                    },
134                    backend_data: Some(extra),
135                }))
136            }
137        } else {
138            let str_msg = match frame_status {
139                vmbc_sys::VmbFrameStatusType::VmbFrameStatusIncomplete => {
140                    "Frame could not be filled to the end"
141                }
142                vmbc_sys::VmbFrameStatusType::VmbFrameStatusTooSmall => {
143                    "Frame buffer was too small"
144                }
145                vmbc_sys::VmbFrameStatusType::VmbFrameStatusInvalid => "Frame buffer was invalid",
146                other => {
147                    if other == -4 {
148                        eprintln!("undocumented frame status -4: was VmbShutdown() called?");
149                    }
150                    panic!("undocumented frame status received {}", other);
151                }
152            };
153            Err(ci2::Error::SingleFrameError(str_msg.into()))
154        };
155
156        // Enqueue frame again.
157        let err_code = {
158            unsafe {
159                vimba_lib()
160                    .vimba_lib
161                    .VmbCaptureFrameQueue(camera_handle, frame, Some(callback_c))
162            }
163        };
164
165        if err_code != vmbc_sys::VmbErrorType::VmbErrorSuccess {
166            let e = vimba::Error::from(vimba::VimbaError::from(err_code));
167            return Err(ve2ce(e));
168        }
169
170        let tx = {
171            // In this scope, we keep the lock on the SENDERS mutex.
172            let vec_senders = &mut *SENDERS.lock().unwrap();
173            if let Some(idx) = vec_senders
174                .iter()
175                .position(|x| x.handle.inner == camera_handle)
176            {
177                let sender = &vec_senders[idx];
178                sender.tx.clone()
179            } else {
180                return Err(ci2::Error::from(format!(
181                    "CB: no sender found for camera: {:?}",
182                    camera_handle
183                )));
184            }
185        };
186
187        match tx.try_send(msg) {
188            Ok(()) => {}
189            Err(std::sync::mpsc::TrySendError::Full(_msg)) => {
190                warn!("channel full");
191            }
192            Err(std::sync::mpsc::TrySendError::Disconnected(_frame_result)) => {
193                error!("disconnected channel");
194                IS_DONE.store(true, Ordering::Relaxed); // indicate we are done
195            }
196        }
197    }
198    Ok(())
199}
200
201/// # Safety
202///
203/// This function will not propagate panics that happen in the callback, but it
204/// should print an error to stderr and then soon stop further image-ready
205/// callbacks.
206#[unsafe(no_mangle)]
207pub unsafe extern "C" fn callback_c(
208    camera_handle: vmbc_sys::VmbHandle_t,
209    _stream_handle: vmbc_sys::VmbHandle_t,
210    frame: *mut vmbc_sys::VmbFrame_t,
211) {
212    match std::panic::catch_unwind(|| {
213        callback_rust(camera_handle, frame).unwrap();
214    }) {
215        Ok(()) => {}
216        Err(e) => {
217            eprintln!("CB: Error: Panic {:?}", e);
218            IS_DONE.store(true, Ordering::Relaxed); // indicate we are done.
219        }
220    }
221}
222
223trait ExtendedError<T> {
224    fn map_vimba_err(self) -> ci2::Result<T>;
225}
226
227impl<T> ExtendedError<T> for std::result::Result<T, vimba::Error> {
228    fn map_vimba_err(self) -> ci2::Result<T> {
229        self.map_err(ve2ce)
230    }
231}
232
233pub type Result<M> = std::result::Result<M, vimba::Error>;
234
235#[derive(Clone)]
236pub struct WrappedModule {}
237
238impl WrappedModule {
239    fn camera_infos(&self) -> ci2::Result<Vec<VimbaCameraInfo>> {
240        let n_cams = vimba_lib().n_cameras().map_vimba_err()?;
241        let vimba_infos = vimba_lib().camera_info(n_cams).map_vimba_err()?;
242
243        let infos = vimba_infos
244            .into_iter()
245            .map(|info| {
246                let serial = info.serial_string;
247                let model = info.camera_name;
248                let vendor = "Allied Vision".to_string(); // TODO: read this
249                let name = info.camera_id_string;
250                VimbaCameraInfo {
251                    name,
252                    serial,
253                    model,
254                    vendor,
255                }
256            })
257            .collect();
258        Ok(infos)
259    }
260}
261
262/// Whether `dir` contains a Vimba GenTL transport layer (a `Vimba*.cti` file).
263fn dir_has_vimba_transport_layer(dir: &std::path::Path) -> bool {
264    let Ok(entries) = std::fs::read_dir(dir) else {
265        return false;
266    };
267    entries.flatten().any(|entry| {
268        let name = entry.file_name();
269        let name = name.to_string_lossy();
270        name.starts_with("Vimba") && name.ends_with(".cti")
271    })
272}
273
274/// A hint about `GENICAM_GENTL64_PATH` if no Vimba GenTL transport layer appears
275/// to be reachable through it.
276///
277/// The Vimba SDK locates its GenTL *transport layers* (`Vimba*.cti` files) via
278/// the `GENICAM_GENTL64_PATH` environment variable. If that variable is unset,
279/// or set but does not include a directory containing a Vimba transport layer
280/// (for example it only lists another vendor's GenTL producers), `VmbStartup`
281/// fails with `VmbErrorNoTL`. In that case this returns a hint to finish the
282/// Vimba SDK installation; otherwise it returns `None`.
283fn gentl_path_hint() -> Option<String> {
284    let has_vimba_tl = std::env::var_os("GENICAM_GENTL64_PATH")
285        .map(|paths| std::env::split_paths(&paths).any(|dir| dir_has_vimba_transport_layer(&dir)))
286        .unwrap_or(false);
287    if has_vimba_tl {
288        return None;
289    }
290
291    let installer = std::cfg_select! {
292        target_os = "linux" => "Complete the Vimba SDK installation by running its GenTL path \
293            installer (for example `sudo /opt/VimbaX_2024-1/cti/Install_GenTL_Path.sh`) and \
294            then starting a new login shell so that `/etc/profile.d/VimbaX_GenTL_Path_64bit.sh` \
295            sets the variable.",
296        _ => "Complete the Vimba SDK installation as described in Allied Vision's \
297            documentation so that this variable is set.",
298    };
299
300    Some(format!(
301        "The GENICAM_GENTL64_PATH environment variable does not point to a Vimba GenTL \
302         transport layer, which is the usual cause of VmbErrorNoTL. {installer}"
303    ))
304}
305
306pub fn new_module() -> ci2::Result<WrappedModule> {
307    // Load the Vimba SDK now, so that a missing or unloadable SDK is reported as
308    // a clean error here rather than panicking on first use deeper in the code.
309    // This is the single fallible initialization point; all later accesses go
310    // through the infallible [vimba_lib].
311    if VIMBA_LIB.get().is_none() {
312        let lib = vimba::VimbaLibrary::new().map_err(|e| {
313            let mut msg = format!(
314                "Could not initialize the Allied Vision Vimba SDK (is it installed?). \
315                 The underlying error was: {e:?}"
316            );
317            if let Some(hint) = gentl_path_hint() {
318                msg = format!("{msg}\n\nHint: {hint}");
319            }
320            ci2::Error::from(anyhow::anyhow!(msg))
321        })?;
322        // Ignore the error if another thread won the race: in that case `lib` is
323        // dropped here. `new_module` is effectively called once per process, so
324        // this does not happen in practice.
325        let _ = VIMBA_LIB.set(lib);
326    }
327    Ok(WrappedModule {})
328}
329
330pub struct VimbaTerminateGuard {
331    already_dropped: bool,
332}
333
334impl Drop for VimbaTerminateGuard {
335    fn drop(&mut self) {
336        if !self.already_dropped {
337            // Only shut down if the library was actually loaded. Use `get()`
338            // (not the panicking `vimba_lib()`) so dropping never panics.
339            if let Some(lib) = VIMBA_LIB.get() {
340                unsafe {
341                    lib.shutdown();
342                }
343            }
344            self.already_dropped = true;
345        }
346    }
347}
348
349pub fn make_singleton_guard(
350    _vimba_module: &dyn ci2::CameraModule<CameraType = WrappedCamera<'static>, Guard = VimbaTerminateGuard>,
351) -> ci2::Result<VimbaTerminateGuard> {
352    Ok(VimbaTerminateGuard {
353        already_dropped: false,
354    })
355}
356
357impl<'a> ci2::CameraModule for &'a WrappedModule {
358    // The camera borrows from `VIMBA_LIB`, which is a `'static` `OnceLock`, so
359    // the camera type carries no borrow from the module reference. Pinning it to
360    // `'static` makes this backend's shape match the Pylon backend (no lifetime
361    // parameter on the camera type).
362    type CameraType = WrappedCamera<'static>;
363    type Guard = VimbaTerminateGuard;
364
365    fn name(self: &&'a WrappedModule) -> &'static str {
366        "vimba"
367    }
368    fn camera_infos(self: &&'a WrappedModule) -> ci2::Result<Vec<Box<dyn ci2::CameraInfo>>> {
369        let vec1 = WrappedModule::camera_infos(self)?;
370        let infos = vec1
371            .into_iter()
372            .map(|vci| {
373                let pci = Box::new(vci);
374                let ci: Box<dyn ci2::CameraInfo> = pci; // explicitly perform type erasure
375                ci
376            })
377            .collect();
378        Ok(infos)
379    }
380    fn camera(self: &mut &'a WrappedModule, name: &str) -> ci2::Result<Self::CameraType> {
381        let camera = vimba::Camera::open(name, vimba::access_mode::FULL, &vimba_lib().vimba_lib)
382            .map_vimba_err()?;
383
384        let vimba_infos = WrappedModule::camera_infos(self)?;
385        let mut my_info = None;
386        for ci in vimba_infos.into_iter() {
387            if ci.name.as_str() == name {
388                my_info = Some(ci);
389                break;
390            }
391        }
392        let info = my_info.unwrap();
393
394        let rx = {
395            // In this scope, we keep the lock on the SENDERS mutex.
396            let vec_senders = &mut *SENDERS.lock().unwrap();
397            let (tx, rx) = std::sync::mpsc::sync_channel(N_CHANNEL_FRAMES);
398            let sender = FrameSender {
399                handle: CamHandle {
400                    inner: camera.handle(),
401                },
402                tx,
403            };
404            vec_senders.push(sender);
405            rx
406        };
407
408        Ok(WrappedCamera {
409            camera: Arc::new(Mutex::new(camera)),
410            acquisition_started: false,
411            info,
412            frames: Vec::with_capacity(N_BUFFER_FRAMES),
413            rx,
414            store_fno: 0,
415        })
416    }
417
418    fn settings_file_extension(&self) -> &str {
419        "xml"
420    }
421}
422
423#[derive(Debug)]
424pub struct VimbaCameraInfo {
425    name: String,
426    serial: String,
427    model: String,
428    vendor: String,
429}
430
431impl ci2::CameraInfo for VimbaCameraInfo {
432    fn name(&self) -> &str {
433        &self.name
434    }
435    fn serial(&self) -> &str {
436        &self.serial
437    }
438    fn model(&self) -> &str {
439        &self.model
440    }
441    fn vendor(&self) -> &str {
442        &self.vendor
443    }
444}
445
446/// newtype to indicate that framenumber must be updated
447struct InvalidHostFramenumber(DynamicFrameWithInfo);
448
449impl InvalidHostFramenumber {
450    fn into_valid(self, fno: usize) -> DynamicFrameWithInfo {
451        let mut result = self.0;
452        result.host_timing.fno = fno;
453        result
454    }
455}
456
457pub struct WrappedCamera<'lib> {
458    pub camera: Arc<Mutex<vimba::Camera<'lib>>>,
459    pub info: VimbaCameraInfo,
460    acquisition_started: bool,
461    frames: Vec<vimba::Frame>,
462    rx: Receiver<std::result::Result<InvalidHostFramenumber, ci2::Error>>,
463    store_fno: usize,
464}
465
466fn _test_camera_is_send() {
467    // Compile-time test to ensure WrappedCamera implements Send trait.
468    fn implements<T: Send>() {}
469    implements::<WrappedCamera>();
470}
471
472impl<'lib> ci2::CameraInfo for WrappedCamera<'lib> {
473    fn name(&self) -> &str {
474        self.info.name()
475    }
476    fn serial(&self) -> &str {
477        self.info.serial()
478    }
479    fn model(&self) -> &str {
480        self.info.model()
481    }
482    fn vendor(&self) -> &str {
483        self.info.vendor()
484    }
485}
486
487impl<'lib> ci2::Camera for WrappedCamera<'lib> {
488    // ----- start: weakly typed but easier to implement API -----
489
490    // fn feature_access_query(&self, name: &str) -> ci2::Result<ci2::AccessQueryResult> {
491    //     let (is_readable, is_writeable) = self
492    //         .camera
493    //         .lock().unwrap()
494    //         .feature_access_query(name)
495    //         .map_vimba_err()?;
496    //     Ok(ci2::AccessQueryResult {
497    //         is_readable,
498    //         is_writeable,
499    //     })
500    // }
501
502    fn command_execute(&self, name: &str, _verify: bool) -> ci2::Result<()> {
503        self.camera
504            .lock()
505            .unwrap()
506            .command_run(name)
507            .map_vimba_err()
508    }
509
510    fn feature_bool(&self, name: &str) -> ci2::Result<bool> {
511        self.camera
512            .lock()
513            .unwrap()
514            .feature_boolean(name)
515            .map_vimba_err()
516    }
517
518    fn feature_bool_set(&self, name: &str, value: bool) -> ci2::Result<()> {
519        self.camera
520            .lock()
521            .unwrap()
522            .feature_boolean_set(name, value)
523            .map_vimba_err()
524    }
525
526    fn feature_enum(&self, name: &str) -> ci2::Result<String> {
527        self.camera
528            .lock()
529            .unwrap()
530            .feature_enum(name)
531            .map_vimba_err()
532            .map(Into::into)
533    }
534
535    fn feature_enum_set(&self, name: &str, value: &str) -> ci2::Result<()> {
536        self.camera
537            .lock()
538            .unwrap()
539            .feature_enum_set(name, value)
540            .map_vimba_err()
541    }
542
543    fn feature_float(&self, name: &str) -> ci2::Result<f64> {
544        self.camera
545            .lock()
546            .unwrap()
547            .feature_float(name)
548            .map_vimba_err()
549    }
550
551    fn feature_float_set(&self, name: &str, value: f64) -> ci2::Result<()> {
552        self.camera
553            .lock()
554            .unwrap()
555            .feature_float_set(name, value)
556            .map_vimba_err()
557    }
558
559    fn feature_int(&self, name: &str) -> ci2::Result<i64> {
560        self.camera
561            .lock()
562            .unwrap()
563            .feature_int(name)
564            .map_vimba_err()
565    }
566
567    fn feature_int_set(&self, name: &str, value: i64) -> ci2::Result<()> {
568        self.camera
569            .lock()
570            .unwrap()
571            .feature_int_set(name, value)
572            .map_vimba_err()
573    }
574
575    // ----- end: weakly typed but easier to implement API -----
576
577    fn node_map_load(&self, settings: &str) -> std::result::Result<(), ci2::Error> {
578        let dir = tempfile::tempdir()?;
579
580        // write the settings to a file
581        let settings_path = dir.path().join("settings.xml");
582        {
583            use std::io::Write;
584
585            // The temporary file is open for writing in this scope.
586            let mut file = std::fs::File::create(&settings_path)?;
587            file.write_all(settings.as_bytes())?;
588            file.flush()?;
589            // When file goes out of scope, it will be closed.
590        }
591
592        let settings_settings = vimba::default_feature_persist_settings(); // let's get meta. settings to load the settings.
593        self.camera
594            .lock()
595            .unwrap()
596            .camera_settings_load(&settings_path, &settings_settings)
597            .map_vimba_err()
598
599        // tempdir will be closed and removed when it is dropped.
600    }
601
602    fn node_map_save(&self) -> std::result::Result<String, ci2::Error> {
603        let dir = tempfile::tempdir()?;
604
605        // write the settings to a file
606        let settings_path = dir.path().join("settings.xml");
607
608        let settings_settings = vimba::default_feature_persist_settings(); // let's get meta. settings to save the settings.
609        self.camera
610            .lock()
611            .unwrap()
612            .camera_settings_save(&settings_path, &settings_settings)
613            .map_vimba_err()?;
614
615        let buf = std::fs::read_to_string(&settings_path)?;
616        Ok(buf)
617        // tempdir will be closed and removed when it is dropped.
618    }
619
620    fn width(&self) -> std::result::Result<u32, ci2::Error> {
621        Ok(self
622            .camera
623            .lock()
624            .unwrap()
625            .feature_int("Width")
626            .map_vimba_err()?
627            .try_into()?)
628    }
629    fn height(&self) -> std::result::Result<u32, ci2::Error> {
630        Ok(self
631            .camera
632            .lock()
633            .unwrap()
634            .feature_int("Height")
635            .map_vimba_err()?
636            .try_into()?)
637    }
638    fn pixel_format(&self) -> std::result::Result<PixFmt, ci2::Error> {
639        self.camera.lock().unwrap().pixel_format().map_vimba_err()
640    }
641    fn possible_pixel_formats(&self) -> std::result::Result<Vec<PixFmt>, ci2::Error> {
642        let fmts = self
643            .camera
644            .lock()
645            .unwrap()
646            .feature_enum_range_query("PixelFormat")
647            .map_vimba_err()?;
648        Ok(fmts
649            .iter()
650            // This silently drops pixel formats that cannot be converted.
651            .filter_map(|fmt_str| vimba::str_to_pixel_format(fmt_str).map_vimba_err().ok())
652            .collect())
653    }
654    fn set_pixel_format(&mut self, pixfmt: PixFmt) -> std::result::Result<(), ci2::Error> {
655        let pixfmt_vimba = vimba::pixel_format_to_str(pixfmt).map_vimba_err()?;
656        self.camera
657            .lock()
658            .unwrap()
659            .feature_enum_set("PixelFormat", pixfmt_vimba)
660            .map_vimba_err()?;
661        Ok(())
662    }
663    fn exposure_time(&self) -> std::result::Result<f64, ci2::Error> {
664        self.camera
665            .lock()
666            .unwrap()
667            .feature_float("ExposureTime")
668            .map_vimba_err()
669    }
670    fn exposure_time_range(&self) -> std::result::Result<(f64, f64), ci2::Error> {
671        self.camera
672            .lock()
673            .unwrap()
674            .feature_float_range_query("ExposureTime")
675            .map_vimba_err()
676    }
677    fn set_exposure_time(&mut self, value: f64) -> std::result::Result<(), ci2::Error> {
678        self.camera
679            .lock()
680            .unwrap()
681            .feature_float_set("ExposureTime", value)
682            .map_vimba_err()
683    }
684    fn exposure_auto(&self) -> std::result::Result<AutoMode, ci2::Error> {
685        let c = self.camera.lock().unwrap();
686        let mystr = c.feature_enum("ExposureAuto").map_vimba_err()?;
687        str_to_auto_mode(mystr)
688    }
689    fn set_exposure_auto(&mut self, value: AutoMode) -> std::result::Result<(), ci2::Error> {
690        let valstr = auto_mode_to_str(value);
691        let c = self.camera.lock().unwrap();
692        c.feature_enum_set("ExposureAuto", valstr).map_vimba_err()
693    }
694    fn gain(&self) -> std::result::Result<f64, ci2::Error> {
695        self.camera
696            .lock()
697            .unwrap()
698            .feature_float("Gain")
699            .map_vimba_err()
700    }
701    fn gain_range(&self) -> std::result::Result<(f64, f64), ci2::Error> {
702        self.camera
703            .lock()
704            .unwrap()
705            .feature_float_range_query("Gain")
706            .map_vimba_err()
707    }
708    fn set_gain(&mut self, value: f64) -> std::result::Result<(), ci2::Error> {
709        self.camera
710            .lock()
711            .unwrap()
712            .feature_float_set("Gain", value)
713            .map_vimba_err()
714    }
715    fn gain_auto(&self) -> std::result::Result<AutoMode, ci2::Error> {
716        let c = self.camera.lock().unwrap();
717        let mystr = c.feature_enum("GainAuto").map_vimba_err()?;
718        str_to_auto_mode(mystr)
719    }
720    fn set_gain_auto(&mut self, value: AutoMode) -> std::result::Result<(), ci2::Error> {
721        let valstr = auto_mode_to_str(value);
722        let c = self.camera.lock().unwrap();
723        c.feature_enum_set("GainAuto", valstr).map_vimba_err()
724    }
725
726    fn start_default_external_triggering(&mut self) -> std::result::Result<(), ci2::Error> {
727        let restart = if self.acquisition_started {
728            self.acquisition_stop()?;
729            true
730        } else {
731            false
732        };
733
734        // The trigger selector must be set before the trigger mode.
735        self.set_trigger_selector(ci2::TriggerSelector::FrameStart)?;
736        {
737            let c = self.camera.lock().unwrap();
738            c.feature_enum_set("TriggerSource", "Line0")
739                .map_vimba_err()?;
740        }
741        self.set_trigger_mode(ci2::TriggerMode::On)?;
742        if restart {
743            self.acquisition_start()?;
744        }
745        Ok(())
746    }
747
748    fn set_software_frame_rate_limit(
749        &mut self,
750        fps_limit: f64,
751    ) -> std::result::Result<(), ci2::Error> {
752        let restart = if self.acquisition_started {
753            self.acquisition_stop()?;
754            true
755        } else {
756            false
757        };
758
759        self.set_acquisition_frame_rate_enable(true)?;
760        self.set_acquisition_frame_rate(fps_limit)?;
761
762        if restart {
763            self.acquisition_start()?;
764        }
765        Ok(())
766    }
767
768    fn trigger_mode(&self) -> std::result::Result<TriggerMode, ci2::Error> {
769        let c = self.camera.lock().unwrap();
770        let val = c.feature_enum("TriggerMode").map_vimba_err()?;
771        match val {
772            "Off" => Ok(ci2::TriggerMode::Off),
773            "On" => Ok(ci2::TriggerMode::On),
774            s => Err(ci2::Error::from(format!(
775                "unexpected TriggerMode enum string: {}",
776                s
777            ))),
778        }
779    }
780    fn set_trigger_mode(&mut self, val: TriggerMode) -> std::result::Result<(), ci2::Error> {
781        let valstr = match val {
782            ci2::TriggerMode::Off => "Off",
783            ci2::TriggerMode::On => "On",
784        };
785        let c = self.camera.lock().unwrap();
786        c.feature_enum_set("TriggerMode", valstr).map_vimba_err()
787    }
788    fn acquisition_frame_rate_enable(&self) -> std::result::Result<bool, ci2::Error> {
789        self.camera
790            .lock()
791            .unwrap()
792            .feature_boolean("AcquisitionFrameRateEnable")
793            .map_vimba_err()
794    }
795    fn set_acquisition_frame_rate_enable(
796        &mut self,
797        value: bool,
798    ) -> std::result::Result<(), ci2::Error> {
799        self.camera
800            .lock()
801            .unwrap()
802            .feature_boolean_set("AcquisitionFrameRateEnable", value)
803            .map_vimba_err()
804    }
805    fn acquisition_frame_rate(&self) -> std::result::Result<f64, ci2::Error> {
806        self.camera
807            .lock()
808            .unwrap()
809            .feature_float("AcquisitionFrameRate")
810            .map_vimba_err()
811    }
812    fn acquisition_frame_rate_range(&self) -> std::result::Result<(f64, f64), ci2::Error> {
813        self.camera
814            .lock()
815            .unwrap()
816            .feature_float_range_query("AcquisitionFrameRate")
817            .map_vimba_err()
818    }
819    fn set_acquisition_frame_rate(&mut self, value: f64) -> std::result::Result<(), ci2::Error> {
820        self.camera
821            .lock()
822            .unwrap()
823            .feature_float_set("AcquisitionFrameRate", value)
824            .map_vimba_err()
825    }
826    fn trigger_selector(&self) -> std::result::Result<ci2::TriggerSelector, ci2::Error> {
827        let c = self.camera.lock().unwrap();
828        let val = c.feature_enum("TriggerSelector").map_vimba_err()?;
829        match val {
830            "AcquisitionStart" => Ok(ci2::TriggerSelector::AcquisitionStart),
831            "FrameBurstStart" => Ok(ci2::TriggerSelector::FrameBurstStart),
832            "FrameStart" => Ok(ci2::TriggerSelector::FrameStart),
833            "ExposureActive" => Ok(ci2::TriggerSelector::ExposureActive),
834            s => Err(ci2::Error::from(format!(
835                "unexpected TriggerSelector enum string: {}",
836                s
837            ))),
838        }
839    }
840    fn set_trigger_selector(
841        &mut self,
842        val: ci2::TriggerSelector,
843    ) -> std::result::Result<(), ci2::Error> {
844        let valstr = match val {
845            ci2::TriggerSelector::AcquisitionStart => "AcquisitionStart",
846            ci2::TriggerSelector::FrameStart => "FrameStart",
847            ci2::TriggerSelector::FrameBurstStart => "FrameBurstStart",
848            ci2::TriggerSelector::ExposureActive => "ExposureActive",
849            _ => {
850                return Err(ci2::Error::from(format!(
851                    "unknown TriggerSelector mode: {:?}",
852                    val
853                )));
854            }
855        };
856        let c = self.camera.lock().unwrap();
857        c.feature_enum_set("TriggerSelector", valstr)
858            .map_vimba_err()
859    }
860    fn acquisition_mode(&self) -> std::result::Result<AcquisitionMode, ci2::Error> {
861        let val = self
862            .camera
863            .lock()
864            .unwrap()
865            .feature_enum("AcquisitionMode")
866            .map_vimba_err()?;
867        Ok(match val {
868            "Continuous" => AcquisitionMode::Continuous,
869            "SingleFrame" => AcquisitionMode::SingleFrame,
870            "MultiFrame" => AcquisitionMode::MultiFrame,
871            val => {
872                return Err(ci2::Error::from(format!(
873                    "unknown AcquisitionMode: {:?}",
874                    val
875                )));
876            }
877        })
878    }
879    fn set_acquisition_mode(
880        &mut self,
881        value: AcquisitionMode,
882    ) -> std::result::Result<(), ci2::Error> {
883        let modes = self
884            .camera
885            .lock()
886            .unwrap()
887            .feature_enum_range_query("AcquisitionMode")
888            .map_vimba_err()?;
889        println!("modes {:?}", modes);
890
891        let sval = match value {
892            AcquisitionMode::Continuous => "Continuous",
893            AcquisitionMode::SingleFrame => "SingleFrame",
894            AcquisitionMode::MultiFrame => "MultiFrame",
895        };
896        self.camera
897            .lock()
898            .unwrap()
899            .feature_enum_set("AcquisitionMode", sval)
900            .map_vimba_err()
901    }
902    fn acquisition_start(&mut self) -> std::result::Result<(), ci2::Error> {
903        IS_DONE.store(false, Ordering::Relaxed); // indicate we are done
904
905        let camera = self.camera.lock().unwrap();
906
907        for _ in 0..N_BUFFER_FRAMES {
908            let buffer = camera.allocate_buffer().map_vimba_err()?;
909            let mut frame = vimba::Frame::new(buffer);
910            camera.frame_announce(&mut frame).map_vimba_err()?;
911            self.frames.push(frame);
912        }
913
914        // -----
915
916        {
917            camera.capture_start().map_vimba_err()?;
918
919            for frame in self.frames.iter_mut() {
920                camera
921                    .capture_frame_queue_with_callback(frame, Some(callback_c))
922                    .map_vimba_err()?;
923            }
924
925            camera.command_run("AcquisitionStart").map_vimba_err()?;
926        }
927
928        self.acquisition_started = true;
929        Ok(())
930    }
931    fn acquisition_stop(&mut self) -> std::result::Result<(), ci2::Error> {
932        let camera = self.camera.lock().unwrap();
933
934        IS_DONE.store(true, Ordering::Relaxed); // indicate we are done
935
936        {
937            camera.command_run("AcquisitionStop").map_vimba_err()?;
938            camera.capture_end().map_vimba_err()?;
939            camera.capture_queue_flush().map_vimba_err()?;
940            for mut frame in self.frames.drain(..) {
941                camera.frame_revoke(&mut frame).map_vimba_err()?;
942            }
943        }
944        self.acquisition_started = false;
945        Ok(())
946    }
947    fn next_frame(&mut self) -> std::result::Result<DynamicFrameWithInfo, ci2::Error> {
948        let msg = match self.rx.recv() {
949            Ok(msg) => msg,
950            Err(err) => {
951                return Err(ci2::Error::BackendError(anyhow::anyhow!(
952                    "Error receiving frame : {}",
953                    err
954                )));
955            }
956        };
957        let frame = msg?.into_valid(self.store_fno);
958        self.store_fno += 1;
959        Ok(frame)
960    }
961}
962
963fn str_to_auto_mode(val: &str) -> ci2::Result<ci2::AutoMode> {
964    match val {
965        "Off" => Ok(ci2::AutoMode::Off),
966        "Once" => Ok(ci2::AutoMode::Once),
967        "Continuous" => Ok(ci2::AutoMode::Continuous),
968        s => Err(ci2::Error::from(format!(
969            "unexpected AutoMode enum string: {}",
970            s
971        ))),
972    }
973}
974
975fn auto_mode_to_str(value: ci2::AutoMode) -> &'static str {
976    use ci2::AutoMode::*;
977    match value {
978        Off => "Off",
979        Once => "Once",
980        Continuous => "Continuous",
981    }
982}