Skip to main content

playerone_sdk/
camera.rs

1use std::ffi::{c_char, c_int, c_long};
2
3use playerone_sdk_sys::POABool::{POA_FALSE, POA_TRUE};
4use playerone_sdk_sys::POAConfig::{POA_EXPOSURE, POA_GAIN};
5use playerone_sdk_sys::{
6    FromPOAConfigValue, POACameraProperties, POACloseCamera, POAConfigAttributes, POAConfigValue,
7    POAErrors, POAGetCameraCount, POAGetCameraProperties, POAGetConfig, POAGetConfigAttributes,
8    POAGetConfigsCount, POAGetImageBin, POAGetImageData, POAGetImageFormat, POAGetImageSize,
9    POAGetImageStartPos, POAGetSensorMode, POAGetSensorModeCount, POAGetSensorModeInfo,
10    POAImageReady, POAInitCamera, POAOpenCamera, POASensorModeInfo, POASetConfig, POASetEnableDPS,
11    POASetImageBin, POASetImageFormat, POASetImageSize, POASetImageStartPos, POASetSensorMode,
12    POAStartExposure, POAStopExposure, _POABool as POABool, _POAConfig as POAConfig, _POAErrors,
13    _POAImgFormat as POAImgFormat,
14};
15
16use crate::{AllConfigBounds, CameraProperties, Error, ImageFormat, SensorMode};
17
18type POAResult<T> = Result<T, Error>;
19
20/// Region Of Interest
21#[derive(Debug, Copy, Clone)]
22pub struct ROI {
23    pub start_x: u32,
24    pub start_y: u32,
25    pub width: u32,
26    pub height: u32,
27}
28
29/// Description of a camera
30/// Can be used to open the camera and get access to many more functionality
31pub struct CameraDescription {
32    camera_id: i32,
33    properties: CameraProperties,
34}
35
36impl CameraDescription {
37    pub fn camera_id(&self) -> i32 {
38        self.camera_id
39    }
40
41    pub fn properties(&self) -> &CameraProperties {
42        &self.properties
43    }
44
45    pub fn open(self) -> POAResult<Camera> {
46        let mut camera = Camera {
47            camera_id: self.camera_id,
48            closed: false,
49            properties: self.properties,
50        };
51        camera.open()?;
52        Ok(camera)
53    }
54}
55
56#[derive(Debug)]
57pub struct Camera {
58    camera_id: i32,
59    closed: bool,
60    properties: CameraProperties,
61}
62
63impl Drop for Camera {
64    fn drop(&mut self) {
65        if !self.closed {
66            // error can be handled by calling close() manually
67            let _ = unsafe { POACloseCamera(self.camera_id) };
68        }
69    }
70}
71
72impl Camera {
73    /// Returns the list of all available cameras
74    /// Call open() on the CameraDescription to get a Camera instance
75    pub fn all_cameras() -> Vec<CameraDescription> {
76        let camera_count = unsafe { POAGetCameraCount() };
77        let mut cameras = Vec::with_capacity(camera_count as usize);
78
79        for i in 0..camera_count {
80            let mut camera_prop: POACameraProperties = POACameraProperties::default();
81            let error = unsafe { POAGetCameraProperties(i, &raw mut camera_prop) };
82
83            if error != _POAErrors::POA_OK {
84                continue;
85            }
86            cameras.push(CameraDescription {
87                camera_id: camera_prop.cameraID,
88                properties: camera_prop.into(),
89            });
90        }
91
92        cameras
93    }
94
95    fn open(&mut self) -> POAResult<()> {
96        let error = unsafe { POAOpenCamera(self.camera_id) };
97        if error != _POAErrors::POA_OK {
98            return Err(error.into());
99        }
100
101        let error = unsafe { POAInitCamera(self.camera_id) };
102        if error != _POAErrors::POA_OK {
103            unsafe { POACloseCamera(self.camera_id) };
104
105            return Err(error.into());
106        }
107
108        Ok(())
109    }
110
111    /// get a single frame, this function will block or wait for the timeout (in ms)
112    ///
113    /// To get continuous frames, prefer to use the stream() method
114    ///
115    /// the buffer size must be bigger than this: POA_RAW8: width * height, POA_RAW16: width * height * 2, POA_RGB24: width * height * 3
116    pub fn capture(&mut self, buffer: &mut [u8], timeout: Option<i32>) -> POAResult<()> {
117        let error = unsafe { POAStartExposure(self.camera_id, POA_TRUE) };
118        if error != _POAErrors::POA_OK {
119            return Err(error.into());
120        }
121        self.get_image_data(buffer, timeout)?;
122        self.stop_exposure()?;
123        Ok(())
124    }
125
126    /// Calls the callback continuously with the newest image data.
127    /// Stops the stream if the callback returns false.
128    pub fn stream(
129        &mut self,
130        timeout: Option<u32>,
131        mut callback: impl FnMut(&mut Camera, &[u8]) -> bool,
132    ) -> POAResult<()> {
133        if let Some(timeout) = timeout {
134            if timeout > i32::MAX as u32 {
135                return Err(Error::OutOfBounds);
136            }
137        }
138
139        let mut buffer = self.create_image_buffer();
140
141        self.start_exposure()?;
142        loop {
143            match self.get_image_data(&mut buffer, timeout.map(|t| t as i32)) {
144                Ok(_) => (),
145                Err(e) => {
146                    let _ = self.stop_exposure();
147                    return Err(e);
148                }
149            }
150            if !callback(self, &buffer) {
151                break;
152            }
153        }
154
155        self.stop_exposure()?;
156        Ok(())
157    }
158
159    /// Creates a buffer of the proper size to hold the image data
160    pub fn create_image_buffer(&self) -> Vec<u8> {
161        let (w, h) = self.image_size();
162        let format = self.image_format().unwrap();
163        vec![0; w as usize * h as usize * format.bytes_per_pixel()]
164    }
165
166    /// start camera exposure for manual control over frame fetching
167    /// Prefer to use stream() or single_frame() method for easier use.
168    pub fn start_exposure(&mut self) -> POAResult<()> {
169        let error = unsafe { POAStartExposure(self.camera_id, POA_FALSE) };
170        if error != _POAErrors::POA_OK {
171            return Err(error.into());
172        }
173        Ok(())
174    }
175
176    /// the image data is available? if true, you can call get_image_data to get image data
177    pub fn is_image_ready(&self) -> POAResult<bool> {
178        let mut is_img_data_available = POA_FALSE;
179        let error = unsafe { POAImageReady(self.camera_id, &raw mut is_img_data_available) };
180        if error != _POAErrors::POA_OK {
181            return Err(error.into());
182        }
183        Ok(is_img_data_available.into())
184    }
185
186    /// get image data after exposure, this function will block or wait for the timeout (in ms)
187    /// None timeout means infinite blocking
188    ///
189    /// the buffer size must be bigger than this: POA_RAW8: width * height, POA_RAW16: width * height * 2, POA_RGB24: width * height * 3
190    pub fn get_image_data(&self, buffer: &mut [u8], timeout_ms: Option<i32>) -> POAResult<()> {
191        let error = unsafe {
192            POAGetImageData(
193                self.camera_id,
194                buffer.as_mut_ptr(),
195                buffer.len() as c_long,
196                timeout_ms.unwrap_or(-1),
197            )
198        };
199        if error != _POAErrors::POA_OK {
200            return Err(error.into());
201        }
202        Ok(())
203    }
204
205    /// Stops the exposure. Must be called before any other camera operations if start_exposure was called.
206    pub fn stop_exposure(&mut self) -> POAResult<()> {
207        let error = unsafe { POAStopExposure(self.camera_id) };
208        if error != _POAErrors::POA_OK {
209            return Err(error.into());
210        }
211        Ok(())
212    }
213
214    /// Close the camera. This is done automatically on Camera drop but can be called manually if you wish to handle any errors
215    /// that may occur.
216    pub fn close(mut self) -> POAResult<()> {
217        self.closed = true;
218
219        let error = unsafe { POACloseCamera(self.camera_id) };
220        if error != _POAErrors::POA_OK {
221            return Err(error.into());
222        }
223        Ok(())
224    }
225
226    /// Returns the bounds of all the configurations available for this camera
227    /// This is an expensive operation and should not be called frequently
228    pub fn config_bounds(&self) -> AllConfigBounds {
229        let mut config_count = 0;
230        safe_error(unsafe { POAGetConfigsCount(self.camera_id, &raw mut config_count) });
231
232        let mut attributes = Vec::with_capacity(40);
233
234        for i in 0..config_count {
235            let mut conf_attributes = POAConfigAttributes::default();
236
237            safe_error(unsafe {
238                POAGetConfigAttributes(self.camera_id, i, &raw mut conf_attributes)
239            });
240
241            attributes.push(conf_attributes);
242        }
243
244        AllConfigBounds::from(attributes)
245    }
246
247    pub fn set_dps(&mut self, dps: bool) -> POAResult<()> {
248        let b: POABool = dps.into();
249        let error = unsafe { POASetEnableDPS(self.camera_id, &raw const b) };
250        if error != _POAErrors::POA_OK {
251            return Err(error.into());
252        }
253        Ok(())
254    }
255
256    /// Sets the Region Of Interest
257    pub fn set_roi(&mut self, roi_area: &ROI) -> POAResult<()> {
258        self.set_image_size(roi_area.width, roi_area.height)?;
259        self.set_image_start_pos(roi_area.start_x, roi_area.start_y)?;
260        Ok(())
261    }
262
263    /// Gets the Region Of Interest
264    pub fn roi(&self) -> ROI {
265        let start_pos = self.image_start_pos().unwrap();
266        let size = self.image_size();
267
268        ROI {
269            start_x: start_pos.0,
270            start_y: start_pos.1,
271            width: size.0,
272            height: size.1,
273        }
274    }
275
276    /// Must be within max_width and max_height as specified in the camera properties
277    pub fn set_image_size(&mut self, width: u32, height: u32) -> POAResult<()> {
278        if width > self.properties.max_width || height > self.properties.max_height {
279            return Err(Error::OutOfBounds);
280        }
281
282        let error = unsafe { POASetImageSize(self.camera_id, width as c_int, height as c_int) };
283        if error != _POAErrors::POA_OK {
284            return Err(error.into());
285        }
286        Ok(())
287    }
288
289    /// Returns the current image size
290    /// This may change if the binning factor is changed
291    pub fn image_size(&self) -> (u32, u32) {
292        let mut width = 0;
293        let mut height = 0;
294
295        safe_error(unsafe { POAGetImageSize(self.camera_id, &raw mut width, &raw mut height) });
296
297        if width < 0 || height < 0 {
298            panic!("negative image size: {} {}", width, height);
299        }
300
301        (width as u32, height as u32)
302    }
303
304    /// Sets the offset/anchor/start position in the image
305    pub fn set_image_start_pos(&mut self, start_x: u32, start_y: u32) -> POAResult<()> {
306        if start_x > self.properties.max_width || start_y > self.properties.max_height {
307            return Err(Error::OutOfBounds);
308        }
309
310        let error =
311            unsafe { POASetImageStartPos(self.camera_id, start_x as c_int, start_y as c_int) };
312        if error != _POAErrors::POA_OK {
313            return Err(error.into());
314        }
315        Ok(())
316    }
317
318    /// Returns the current image start position
319    /// This may change if the binning factor is changed
320    pub fn image_start_pos(&self) -> POAResult<(u32, u32)> {
321        let mut start_x = 0;
322        let mut start_y = 0;
323
324        safe_error(unsafe {
325            POAGetImageStartPos(self.camera_id, &raw mut start_x, &raw mut start_y)
326        });
327
328        if start_x < 0 || start_y < 0 {
329            panic!("negative image start position: {} {}", start_x, start_y);
330        }
331
332        Ok((start_x as u32, start_y as u32))
333    }
334
335    pub fn set_image_format(&mut self, image_format: ImageFormat) -> POAResult<()> {
336        let poa_img_format = image_format.into();
337
338        let error = unsafe { POASetImageFormat(self.camera_id, poa_img_format) };
339        if error != _POAErrors::POA_OK {
340            return Err(error.into());
341        }
342        Ok(())
343    }
344
345    pub fn image_format(&self) -> POAResult<ImageFormat> {
346        let mut poa_img_format = POAImgFormat::POA_END;
347
348        safe_error(unsafe { POAGetImageFormat(self.camera_id, &raw mut poa_img_format) });
349        Ok(poa_img_format.into())
350    }
351
352    /// Sets the binning factor e.g 1, 2, 4  
353    /// Must be a bin within the available bins in properties  
354    /// The binning function can be average or sum depending on the pixel_bin_sum property (true is sum, false is average). default is average  
355    ///
356    /// Note: If successful, the image size (width & height) and start position will be changed (divided by the binning factor)  
357    /// Call image_size() and image_start_pos() to get the updated values
358    pub fn set_bin(&mut self, bin: u32) -> POAResult<()> {
359        if !self.properties.bins.contains(&bin) {
360            return Err(Error::OutOfBounds);
361        }
362
363        let err = unsafe { POASetImageBin(self.camera_id, bin as c_int) };
364        if err != _POAErrors::POA_OK {
365            return Err(err.into());
366        }
367        Ok(())
368    }
369
370    /// Returns the current binning factor
371    pub fn bin(&self) -> u32 {
372        let mut bin = 0;
373        safe_error(unsafe { POAGetImageBin(self.camera_id, &raw mut bin) });
374        bin as u32
375    }
376
377    /// Enumerate sensor modes advertised by this camera.
378    ///
379    /// Returns an empty vec when the camera does not support mode selection
380    /// (e.g. most entry-level models). Each call queries the hardware.
381    pub fn sensor_modes(&self) -> POAResult<Vec<SensorMode>> {
382        enumerate_sensor_modes(self.camera_id)
383    }
384
385    /// Current sensor-mode index.
386    ///
387    /// Returns [`Error::AccessDenied`] on cameras that do not support mode
388    /// selection.
389    pub fn sensor_mode(&self) -> POAResult<u32> {
390        let mut index: c_int = 0;
391        let err = unsafe { POAGetSensorMode(self.camera_id, &raw mut index) };
392        if err != _POAErrors::POA_OK {
393            return Err(err.into());
394        }
395        if index < 0 {
396            return Err(Error::OperationFailed);
397        }
398        Ok(index as u32)
399    }
400
401    /// Set the active sensor mode by index.
402    ///
403    /// The caller must stop any running exposure before calling this (matches
404    /// the underlying SDK requirement).
405    pub fn set_sensor_mode(&mut self, index: u32) -> POAResult<()> {
406        let err = unsafe { POASetSensorMode(self.camera_id, index as c_int) };
407        if err != _POAErrors::POA_OK {
408            return Err(err.into());
409        }
410        Ok(())
411    }
412
413    pub fn properties(&self) -> &CameraProperties {
414        &self.properties
415    }
416
417    pub fn id(&self) -> i32 {
418        self.camera_id
419    }
420
421    /// Sets the exposure time in microseconds
422    pub fn set_exposure(&mut self, exposure_micros: i64, is_auto: bool) -> POAResult<()> {
423        self.set_config(POA_EXPOSURE, exposure_micros, is_auto)
424    }
425
426    pub fn set_gain(&mut self, gain: i64, is_auto: bool) -> POAResult<()> {
427        self.set_config(POA_GAIN, gain, is_auto)
428    }
429
430    /// Exposure in microseconds and whether it is auto
431    pub fn exposure(&self) -> POAResult<(i64, bool)> {
432        unsafe { self.get_config_auto(POA_EXPOSURE) }
433    }
434
435    /// Gain and whether it is auto
436    pub fn gain(&self) -> POAResult<(i64, bool)> {
437        unsafe { self.get_config_auto(POA_GAIN) }
438    }
439
440    pub fn hardware_bin(&self) -> POAResult<bool> {
441        unsafe { self.get_config(POAConfig::POA_HARDWARE_BIN) }
442    }
443
444    /// Current temperature in Celsius
445    pub fn temperature(&self) -> POAResult<f64> {
446        unsafe { self.get_config(POAConfig::POA_TEMPERATURE) }
447    }
448
449    /// red pixels coefficient of white balance
450    pub fn wb_r(&self) -> POAResult<i64> {
451        unsafe { self.get_config(POAConfig::POA_WB_R) }
452    }
453
454    /// green pixels coefficient of white balance
455    pub fn wb_g(&self) -> POAResult<i64> {
456        unsafe { self.get_config(POAConfig::POA_WB_G) }
457    }
458
459    /// blue pixels coefficient of white balance
460    pub fn wb_b(&self) -> POAResult<i64> {
461        unsafe { self.get_config(POAConfig::POA_WB_B) }
462    }
463
464    pub fn offset(&self) -> POAResult<i64> {
465        unsafe { self.get_config(POAConfig::POA_OFFSET) }
466    }
467
468    /// maximum gain when auto-adjust
469    pub fn auto_max_gain(&self) -> POAResult<i64> {
470        unsafe { self.get_config(POAConfig::POA_AUTOEXPO_MAX_GAIN) }
471    }
472
473    /// maximum exposure when auto-adjust (in ms)
474    pub fn auto_max_exposure_ms(&self) -> POAResult<i64> {
475        unsafe { self.get_config(POAConfig::POA_AUTOEXPO_MAX_EXPOSURE) }
476    }
477
478    /// target brightness when auto-adjust
479    pub fn auto_target_brightness(&self) -> POAResult<i64> {
480        unsafe { self.get_config(POAConfig::POA_AUTOEXPO_BRIGHTNESS) }
481    }
482
483    /// ST4 guide north, generally, it's DEC+ on the mount
484    pub fn guide_north(&self) -> POAResult<bool> {
485        unsafe { self.get_config(POAConfig::POA_GUIDE_NORTH) }
486    }
487
488    /// ST4 guide south, generally, it's DEC- on the mount
489    pub fn guide_south(&self) -> POAResult<bool> {
490        unsafe { self.get_config(POAConfig::POA_GUIDE_SOUTH) }
491    }
492
493    /// ST4 guide east, generally, it's RA+ on the mount
494    pub fn guide_east(&self) -> POAResult<bool> {
495        unsafe { self.get_config(POAConfig::POA_GUIDE_EAST) }
496    }
497
498    /// ST4 guide west, generally, it's RA- on the mount
499    pub fn guide_west(&self) -> POAResult<bool> {
500        unsafe { self.get_config(POAConfig::POA_GUIDE_WEST) }
501    }
502
503    /// e/ADU, This value will change with gain
504    pub fn egain(&self) -> POAResult<f64> {
505        unsafe { self.get_config(POAConfig::POA_EGAIN) }
506    }
507
508    /// cooler power percentage[0-100%](only cool camera)
509    pub fn cooler_power(&self) -> POAResult<i64> {
510        unsafe { self.get_config(POAConfig::POA_COOLER_POWER) }
511    }
512
513    /// camera target temperature (in Celsius)
514    pub fn target_temp(&self) -> POAResult<i64> {
515        unsafe { self.get_config(POAConfig::POA_TARGET_TEMP) }
516    }
517
518    /// is cooler(and fan) on or off
519    pub fn cooler(&self) -> POAResult<bool> {
520        unsafe { self.get_config(POAConfig::POA_COOLER) }
521    }
522
523    #[deprecated]
524    /// get state of lens heater(on or off)
525    pub fn heater(&self) -> POAResult<bool> {
526        unsafe { self.get_config(POAConfig::POA_HEATER) }
527    }
528
529    /// lens heater power percentage[0-100%]
530    pub fn heater_power(&self) -> POAResult<i64> {
531        unsafe { self.get_config(POAConfig::POA_HEATER_POWER) }
532    }
533
534    /// radiator fan power percentage[0-100%]
535    pub fn fan_power(&self) -> POAResult<i64> {
536        unsafe { self.get_config(POAConfig::POA_FAN_POWER) }
537    }
538
539    /// Range is [0, 2000]
540    /// 0 means no limit
541    pub fn frame_limit(&self) -> POAResult<i64> {
542        unsafe { self.get_config(POAConfig::POA_FRAME_LIMIT) }
543    }
544
545    /// High Quality Image, for those without DDR camera(guide camera)
546    /// if true, this will reduce the waviness and stripe of the image
547    pub fn hqi(&self) -> POAResult<bool> {
548        unsafe { self.get_config(POAConfig::POA_HQI) }
549    }
550
551    /// 0-100% usage of USB bandwidth
552    pub fn usb_bandwidth_limit(&self) -> POAResult<i64> {
553        unsafe { self.get_config(POAConfig::POA_USB_BANDWIDTH_LIMIT) }
554    }
555
556    /// take the sum or average of pixels after binning, true is sum and false is average, default is false
557    pub fn pixel_bin_sum(&self) -> POAResult<bool> {
558        unsafe { self.get_config(POAConfig::POA_PIXEL_BIN_SUM) }
559    }
560
561    /// only for color camera, when set to true, pixel binning will use neighbour pixels and image
562    /// after binning will lose the bayer pattern
563    pub fn mono_bin(&self) -> POAResult<bool> {
564        unsafe { self.get_config(POAConfig::POA_MONO_BIN) }
565    }
566
567    pub fn set_hardware_bin(&mut self, value: bool) -> POAResult<()> {
568        self.set_config(POAConfig::POA_HARDWARE_BIN, value, false)
569    }
570
571    /// set the red pixels coefficient of white balance
572    pub fn set_wb_r(&mut self, value: i64) -> POAResult<()> {
573        self.set_config(POAConfig::POA_WB_R, value, false)
574    }
575
576    /// set the green pixels coefficient of white balance
577    pub fn set_wb_g(&mut self, value: i64) -> POAResult<()> {
578        self.set_config(POAConfig::POA_WB_G, value, false)
579    }
580
581    /// set the blue pixels coefficient of white balance
582    pub fn set_wb_b(&mut self, value: i64) -> POAResult<()> {
583        self.set_config(POAConfig::POA_WB_B, value, false)
584    }
585
586    pub fn set_offset(&mut self, value: i64) -> POAResult<()> {
587        self.set_config(POAConfig::POA_OFFSET, value, false)
588    }
589
590    /// set the max gain when auto-adjust
591    pub fn set_auto_max_gain(&mut self, value: i64) -> POAResult<()> {
592        self.set_config(POAConfig::POA_AUTOEXPO_MAX_GAIN, value, false)
593    }
594
595    /// set the max exposure when auto-adjust (in ms)
596    pub fn set_auto_max_exposure_ms(&mut self, value: i64) -> POAResult<()> {
597        self.set_config(POAConfig::POA_AUTOEXPO_MAX_EXPOSURE, value, false)
598    }
599
600    /// set the target brightness when auto-adjust
601    pub fn set_auto_target_brightness(&mut self, value: i64) -> POAResult<()> {
602        self.set_config(POAConfig::POA_AUTOEXPO_BRIGHTNESS, value, false)
603    }
604
605    /// set ST4 guide north, generally, it's DEC+ on the mount
606    pub fn set_guide_north(&mut self, value: bool) -> POAResult<()> {
607        self.set_config(POAConfig::POA_GUIDE_NORTH, value, false)
608    }
609
610    /// set ST4 guide south, generally, it's DEC- on the mount
611    pub fn set_guide_south(&mut self, value: bool) -> POAResult<()> {
612        self.set_config(POAConfig::POA_GUIDE_SOUTH, value, false)
613    }
614
615    /// set ST4 guide east, generally, it's RA+ on the mount
616    pub fn set_guide_east(&mut self, value: bool) -> POAResult<()> {
617        self.set_config(POAConfig::POA_GUIDE_EAST, value, false)
618    }
619
620    /// set ST4 guide west, generally, it's RA- on the mount
621    pub fn set_guide_west(&mut self, value: bool) -> POAResult<()> {
622        self.set_config(POAConfig::POA_GUIDE_WEST, value, false)
623    }
624
625    /// set the camera target temperature (in Celsius)
626    pub fn set_target_temperature(&mut self, value: i64) -> POAResult<()> {
627        self.set_config(POAConfig::POA_TARGET_TEMP, value, false)
628    }
629
630    /// set the cooler(and fan) on or off
631    pub fn set_cooler(&mut self, value: bool) -> POAResult<()> {
632        self.set_config(POAConfig::POA_COOLER, value, false)
633    }
634
635    /// set the state of lens heater(on or off)
636    #[deprecated]
637    pub fn set_heater(&mut self, value: bool) -> POAResult<()> {
638        self.set_config(POAConfig::POA_HEATER, value, false)
639    }
640
641    /// set the lens heater power percentage[0-100%]
642    pub fn set_heater_power(&mut self, value: i64) -> POAResult<()> {
643        self.set_config(POAConfig::POA_HEATER_POWER, value, false)
644    }
645
646    /// set the radiator fan power percentage[0-100%]
647    pub fn set_fan_power(&mut self, value: i64) -> POAResult<()> {
648        self.set_config(POAConfig::POA_FAN_POWER, value, false)
649    }
650
651    /// set the frame limit
652    /// Range is [0, 2000]. 0 means no limit
653    pub fn set_frame_limit(&mut self, value: i64) -> POAResult<()> {
654        self.set_config(POAConfig::POA_FRAME_LIMIT, value, false)
655    }
656
657    /// set High Quality Image, for those without DDR camera(guide camera)
658    /// if true, this will reduce the waviness and stripe of the image but frame rate may go down
659    /// note: this config has no effect on cameras with DDR
660    pub fn set_hqi(&mut self, value: bool) -> POAResult<()> {
661        self.set_config(POAConfig::POA_HQI, value, false)
662    }
663
664    /// set the maximum usage of USB bandwidth [0-100%]
665    pub fn set_usb_bandwidth_limit(&mut self, value: i64) -> POAResult<()> {
666        self.set_config(POAConfig::POA_USB_BANDWIDTH_LIMIT, value, false)
667    }
668
669    /// set whether to take the sum or average of pixels after binning, true is sum and false is average, default is false
670    pub fn set_pixel_bin_sum(&mut self, value: bool) -> POAResult<()> {
671        self.set_config(POAConfig::POA_PIXEL_BIN_SUM, value, false)
672    }
673
674    /// only for color camera: if true,  pixel binning will use neighbour pixels
675    /// and image after binning will lose the bayer pattern
676    pub fn set_mono_bin(&mut self, value: bool) -> POAResult<()> {
677        self.set_config(POAConfig::POA_MONO_BIN, value, false)
678    }
679
680    fn set_config(
681        &mut self,
682        poa_config: POAConfig,
683
684        value: impl Into<POAConfigValue>,
685        is_auto: bool,
686    ) -> POAResult<()> {
687        let value = value.into();
688        let error = unsafe { POASetConfig(self.camera_id, poa_config, value, is_auto.into()) };
689        if error != _POAErrors::POA_OK {
690            return Err(error.into());
691        }
692        Ok(())
693    }
694
695    /// # Unsafe
696    ///
697    /// The given type must match the actual type of the config value
698    unsafe fn get_config_auto<T: FromPOAConfigValue>(
699        &self,
700        poa_config: POAConfig,
701    ) -> POAResult<(T, bool)> {
702        let mut config_value = POAConfigValue::default();
703        let mut is_auto = POABool::POA_FALSE;
704
705        let error = unsafe {
706            POAGetConfig(
707                self.camera_id,
708                poa_config,
709                &raw mut config_value,
710                &raw mut is_auto,
711            )
712        };
713        if error != _POAErrors::POA_OK {
714            return Err(error.into());
715        }
716
717        Ok((
718            FromPOAConfigValue::from_poa_config_value(config_value),
719            is_auto.into(),
720        ))
721    }
722
723    /// # Unsafe
724    ///
725    /// The given type must match the actual type of the config value
726    unsafe fn get_config<T: FromPOAConfigValue>(&self, poa_config: POAConfig) -> POAResult<T> {
727        self.get_config_auto(poa_config).map(|(value, _)| value)
728    }
729}
730
731/// a lot of functions should never fail by construction: pointer are not null, camera_id is valid
732/// and camera is open! so we can safely ignore a lot of api errors
733fn safe_error(error: POAErrors) {
734    if error == _POAErrors::POA_OK {
735        return;
736    }
737    panic!("unexpected POA error: {}", Error::from(error));
738}
739
740/// Enumerate sensor modes for an opened camera.
741///
742/// Returns an empty vec when the mode count is zero (camera does not support
743/// mode selection). Propagates SDK errors via `POAResult`.
744fn enumerate_sensor_modes(camera_id: i32) -> POAResult<Vec<SensorMode>> {
745    let mut count: c_int = 0;
746    let err = unsafe { POAGetSensorModeCount(camera_id, &raw mut count) };
747    if err != _POAErrors::POA_OK {
748        return Err(err.into());
749    }
750    if count <= 0 {
751        return Ok(Vec::new());
752    }
753
754    let mut modes = Vec::with_capacity(count as usize);
755    for index in 0..count {
756        let mut info = POASensorModeInfo::default();
757        let err = unsafe { POAGetSensorModeInfo(camera_id, index, &raw mut info) };
758        if err != _POAErrors::POA_OK {
759            return Err(err.into());
760        }
761        modes.push(SensorMode {
762            index: index as u32,
763            name: c_char_array_to_string(&info.name),
764            description: c_char_array_to_string(&info.desc),
765        });
766    }
767    Ok(modes)
768}
769
770/// Decode a fixed-size C char buffer that may or may not be NUL-terminated.
771/// Safer than `CStr::from_ptr` here because the SDK fills `POASensorModeInfo`
772/// fields without a length and a pathological firmware could in principle
773/// write exactly `buf.len()` bytes with no trailing NUL.
774fn c_char_array_to_string(buf: &[c_char]) -> String {
775    let nul = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
776    let bytes: Vec<u8> = buf[..nul].iter().map(|&c| c as u8).collect();
777    String::from_utf8_lossy(&bytes).trim().to_string()
778}