generic_camera/
property.rs

1/*!
2 * # Property
3 * Encapsulates values and limits of a property.
4 */
5use std::time::Duration;
6
7use crate::GenCamPixelBpp;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Result type for property operations
12pub type PropertyResult<T> = Result<T, PropertyError>;
13
14#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15/// A property
16pub struct Property {
17    auto: bool,
18    rdonly: bool,
19    prop: PropertyLims,
20    doc: Option<String>,
21}
22
23impl Property {
24    /// Create a new property
25    pub fn new(prop: PropertyLims, auto_supported: bool, rdonly: bool) -> Self {
26        Property {
27            auto: auto_supported,
28            rdonly,
29            prop,
30            doc: None,
31        }
32    }
33
34    /// Set an optional documentation string
35    pub fn set_doc<T: Into<String>>(&mut self, doc: T) {
36        self.doc = Some(doc.into());
37    }
38
39    /// Get the documentation string
40    pub fn get_doc(&self) -> Option<&str> {
41        self.doc.as_deref()
42    }
43
44    /// Get the type of the property
45    pub fn get_type(&self) -> PropertyType {
46        (&self.prop).into()
47    }
48
49    /// Check if the property supports auto mode
50    pub fn supports_auto(&self) -> bool {
51        self.auto
52    }
53
54    /// Validate a property value
55    pub fn validate(&self, value: &PropertyValue) -> PropertyResult<()> {
56        // 1. Check if value in enum
57        match self.prop {
58            PropertyLims::EnumStr { ref variants, .. } => {
59                if let PropertyValue::EnumStr(ref val) = value {
60                    if variants.contains(val) {
61                        return Ok(());
62                    } else {
63                        return Err(PropertyError::ValueNotSupported);
64                    }
65                } else {
66                    return Err(PropertyError::InvalidControlType {
67                        expected: PropertyType::EnumStr,
68                        received: value.get_type(),
69                    });
70                }
71            }
72            PropertyLims::EnumInt { ref variants, .. } => {
73                if let PropertyValue::Int(ref val) = value {
74                    if variants.contains(val) {
75                        return Ok(());
76                    } else {
77                        return Err(PropertyError::ValueNotSupported);
78                    }
79                } else {
80                    return Err(PropertyError::InvalidControlType {
81                        expected: PropertyType::EnumInt,
82                        received: value.get_type(),
83                    });
84                }
85            }
86            PropertyLims::EnumUnsigned { ref variants, .. } => {
87                if let PropertyValue::Unsigned(ref val) = value {
88                    if variants.contains(val) {
89                        return Ok(());
90                    } else {
91                        return Err(PropertyError::ValueNotSupported);
92                    }
93                } else {
94                    return Err(PropertyError::InvalidControlType {
95                        expected: PropertyType::EnumUnsigned,
96                        received: value.get_type(),
97                    });
98                }
99            }
100            PropertyLims::Duration { .. } => {
101                if value.get_type() != PropertyType::Duration {
102                    return Err(PropertyError::InvalidControlType {
103                        expected: PropertyType::Duration,
104                        received: value.get_type(),
105                    });
106                }
107            }
108            PropertyLims::Bool { .. } => {
109                if value.get_type() != PropertyType::Bool {
110                    return Err(PropertyError::InvalidControlType {
111                        expected: PropertyType::Bool,
112                        received: value.get_type(),
113                    });
114                }
115            }
116            PropertyLims::Int { .. } => {
117                if value.get_type() != PropertyType::Int {
118                    return Err(PropertyError::InvalidControlType {
119                        expected: PropertyType::Int,
120                        received: value.get_type(),
121                    });
122                }
123            }
124            PropertyLims::Float { .. } => {
125                if value.get_type() != PropertyType::Float {
126                    return Err(PropertyError::InvalidControlType {
127                        expected: PropertyType::Float,
128                        received: value.get_type(),
129                    });
130                }
131            }
132            PropertyLims::Unsigned { .. } => {
133                if value.get_type() != PropertyType::Unsigned {
134                    return Err(PropertyError::InvalidControlType {
135                        expected: PropertyType::Unsigned,
136                        received: value.get_type(),
137                    });
138                }
139            }
140            PropertyLims::PixelFmt { .. } => {
141                if value.get_type() != PropertyType::PixelFmt {
142                    return Err(PropertyError::InvalidControlType {
143                        expected: PropertyType::PixelFmt,
144                        received: value.get_type(),
145                    });
146                }
147            }
148        }
149        // 2. Check if value is within limits
150        match self.get_type() {
151            PropertyType::Int
152            | PropertyType::Unsigned
153            | PropertyType::Float
154            | PropertyType::Duration => {
155                if &self.get_min()? <= value && value <= &self.get_max()? {
156                    Ok(())
157                } else {
158                    Err(PropertyError::ValueOutOfRange {
159                        value: value.clone(),
160                        min: self.get_min().unwrap(), // safety: checked above
161                        max: self.get_max().unwrap(), // safety: checked above
162                    })
163                }
164            }
165            PropertyType::Bool => Ok(()),
166            PropertyType::Command
167            | PropertyType::PixelFmt
168            | PropertyType::EnumStr
169            | PropertyType::EnumInt
170            | PropertyType::EnumUnsigned => Err(PropertyError::NotNumber),
171        }
172    }
173
174    /// Get the minimum value of the property
175    pub fn get_min(&self) -> PropertyResult<PropertyValue> {
176        use PropertyLims::*;
177        match &self.prop {
178            Bool { .. } => Err(PropertyError::NotNumber),
179            Int { min, .. } => Ok((*min).into()),
180            Float { min, .. } => Ok((*min).into()),
181            Unsigned { min, .. } => Ok((*min).into()),
182            Duration { min, .. } => Ok((*min).into()),
183            PixelFmt { variants, .. } => {
184                Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
185            }
186            EnumStr { .. } => Err(PropertyError::NotNumber),
187            EnumInt { variants, .. } => {
188                Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
189            }
190            EnumUnsigned { variants, .. } => {
191                Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
192            }
193        }
194    }
195
196    /// Get the maximum value of the property
197    pub fn get_max(&self) -> PropertyResult<PropertyValue> {
198        use PropertyLims::*;
199        match &self.prop {
200            Bool { .. } => Err(PropertyError::NotNumber),
201            Int { max, .. } => Ok((*max).into()),
202            Float { max, .. } => Ok((*max).into()),
203            Unsigned { max, .. } => Ok((*max).into()),
204            Duration { max, .. } => Ok((*max).into()),
205            PixelFmt { variants, .. } => {
206                Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
207            }
208            EnumStr { .. } => Err(PropertyError::NotNumber),
209            EnumInt { variants, .. } => {
210                Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
211            }
212            EnumUnsigned { variants, .. } => {
213                Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
214            }
215        }
216    }
217
218    /// Get the step value of the property
219    pub fn get_step(&self) -> PropertyResult<PropertyValue> {
220        use PropertyLims::*;
221        match &self.prop {
222            Bool { .. } => Err(PropertyError::NotNumber),
223            Int { step, .. } => Ok((*step).into()),
224            Float { step, .. } => Ok((*step).into()),
225            Unsigned { step, .. } => Ok((*step).into()),
226            Duration { step, .. } => Ok((*step).into()),
227            PixelFmt { .. } => Err(PropertyError::IsEnum),
228            EnumStr { .. } => Err(PropertyError::NotNumber),
229            EnumInt { .. } => Err(PropertyError::IsEnum),
230            EnumUnsigned { .. } => Err(PropertyError::IsEnum),
231        }
232    }
233
234    /// Get the default value of the property
235    pub fn get_default(&self) -> PropertyResult<PropertyValue> {
236        use PropertyLims::*;
237        match self.prop.clone() {
238            Bool { default } => Ok(default.into()),
239            Int { default, .. } => Ok(default.into()),
240            Float { default, .. } => Ok(default.into()),
241            Unsigned { default, .. } => Ok(default.into()),
242            Duration { default, .. } => Ok(default.into()),
243            PixelFmt { default, .. } => Ok(default.into()),
244            EnumStr { default, .. } => Ok(default.into()),
245            EnumInt { default, .. } => Ok(default.into()),
246            EnumUnsigned { default, .. } => Ok(default.into()),
247        }
248    }
249
250    /// Get the variants of the property
251    pub fn get_variants(&self) -> PropertyResult<Vec<PropertyValue>> {
252        use PropertyLims::*;
253        match &self.prop {
254            Bool { .. } | Int { .. } | Float { .. } | Unsigned { .. } | Duration { .. } => {
255                Err(PropertyError::NotEnum)
256            }
257            PixelFmt { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
258            EnumStr { variants, .. } => Ok(variants.iter().map(|x| x.clone().into()).collect()),
259            EnumInt { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
260            EnumUnsigned { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
261        }
262    }
263}
264
265#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266#[non_exhaustive]
267/// A property with limits
268pub enum PropertyLims {
269    /// A boolean property
270    Bool {
271        /// The default value
272        default: bool,
273    },
274    /// An integer property
275    Int {
276        /// The minimum value
277        min: i64,
278        /// The maximum value
279        max: i64,
280        /// The step size
281        step: i64,
282        /// The default value
283        default: i64,
284    },
285    /// A floating point property
286    Float {
287        /// The minimum value
288        min: f64,
289        /// The maximum value
290        max: f64,
291        /// The step size
292        step: f64,
293        /// The default value
294        default: f64,
295    },
296    /// An unsigned integer property
297    Unsigned {
298        /// The minimum value
299        min: u64,
300        /// The maximum value
301        max: u64,
302        /// The step size
303        step: u64,
304        /// The default value
305        default: u64,
306    },
307    /// A duration property
308    Duration {
309        /// The minimum value
310        min: Duration,
311        /// The maximum value
312        max: Duration,
313        /// The step size
314        step: Duration,
315        /// The default value
316        default: Duration,
317    },
318    /// A pixel format property
319    PixelFmt {
320        /// The variants of the property
321        variants: Vec<GenCamPixelBpp>,
322        /// The default value
323        default: GenCamPixelBpp,
324    },
325    /// An enum string property
326    EnumStr {
327        /// The variants of the property
328        variants: Vec<String>,
329        /// The default value
330        default: String,
331    },
332    /// An enum integer property
333    EnumInt {
334        /// The variants of the property
335        variants: Vec<i64>,
336        /// The default value
337        default: i64,
338    },
339    /// An enum unsigned integer property
340    EnumUnsigned {
341        /// The variants of the property
342        variants: Vec<u64>,
343        /// The default value
344        default: u64,
345    },
346}
347
348#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, PartialOrd)]
349#[non_exhaustive]
350/// A property value
351pub enum PropertyValue {
352    /// A command
353    Command,
354    /// A boolean value
355    Bool(bool),
356    /// An integer value
357    Int(i64),
358    /// A floating point value
359    Float(f64),
360    /// An unsigned integer value
361    Unsigned(u64),
362    /// A pixel format value
363    PixelFmt(GenCamPixelBpp),
364    /// A duration value
365    Duration(Duration),
366    /// An enum string value
367    EnumStr(String),
368}
369
370impl PropertyValue {
371    /// Get the type of the property value
372    pub fn get_type(&self) -> PropertyType {
373        self.into()
374    }
375}
376
377impl From<()> for PropertyValue {
378    fn from(_: ()) -> Self {
379        PropertyValue::Command
380    }
381}
382
383impl From<i64> for PropertyValue {
384    fn from(val: i64) -> Self {
385        PropertyValue::Int(val)
386    }
387}
388
389impl From<u64> for PropertyValue {
390    fn from(val: u64) -> Self {
391        PropertyValue::Unsigned(val)
392    }
393}
394
395impl From<f64> for PropertyValue {
396    fn from(val: f64) -> Self {
397        PropertyValue::Float(val)
398    }
399}
400
401impl From<Duration> for PropertyValue {
402    fn from(val: Duration) -> Self {
403        PropertyValue::Duration(val)
404    }
405}
406
407impl From<String> for PropertyValue {
408    fn from(val: String) -> Self {
409        PropertyValue::EnumStr(val)
410    }
411}
412
413impl From<&str> for PropertyValue {
414    fn from(val: &str) -> Self {
415        PropertyValue::EnumStr(val.to_owned())
416    }
417}
418
419impl From<bool> for PropertyValue {
420    fn from(val: bool) -> Self {
421        PropertyValue::Bool(val)
422    }
423}
424
425impl From<GenCamPixelBpp> for PropertyValue {
426    fn from(val: GenCamPixelBpp) -> Self {
427        PropertyValue::PixelFmt(val)
428    }
429}
430
431impl TryFrom<PropertyValue> for () {
432    type Error = PropertyError;
433
434    fn try_from(value: PropertyValue) -> Result<Self, Self::Error> {
435        match value {
436            PropertyValue::Command => Ok(()),
437            _ => Err(PropertyError::InvalidControlType {
438                expected: PropertyType::Command,
439                received: value.get_type(),
440            }),
441        }
442    }
443}
444
445impl TryFrom<&PropertyValue> for () {
446    type Error = PropertyError;
447
448    fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
449        match value {
450            PropertyValue::Command => Ok(()),
451            _ => Err(PropertyError::InvalidControlType {
452                expected: PropertyType::Command,
453                received: value.get_type(),
454            }),
455        }
456    }
457}
458
459macro_rules! tryfrom_impl_propval {
460    ($type:ty, $variant:ident) => {
461        impl TryFrom<PropertyValue> for $type {
462            type Error = PropertyError;
463
464            fn try_from(value: PropertyValue) -> Result<Self, Self::Error> {
465                match value {
466                    PropertyValue::$variant(val) => Ok(val),
467                    _ => Err(PropertyError::InvalidControlType {
468                        expected: PropertyType::$variant,
469                        received: value.get_type(),
470                    }),
471                }
472            }
473        }
474    };
475}
476
477tryfrom_impl_propval!(bool, Bool);
478tryfrom_impl_propval!(i64, Int);
479tryfrom_impl_propval!(f64, Float);
480tryfrom_impl_propval!(u64, Unsigned);
481tryfrom_impl_propval!(Duration, Duration);
482tryfrom_impl_propval!(String, EnumStr);
483tryfrom_impl_propval!(GenCamPixelBpp, PixelFmt);
484
485macro_rules! tryfrom_impl_propvalref {
486    ($type:ty, $variant:ident) => {
487        impl TryFrom<&PropertyValue> for $type {
488            type Error = PropertyError;
489
490            fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
491                match value {
492                    PropertyValue::$variant(val) => Ok(val.clone()),
493                    _ => Err(PropertyError::InvalidControlType {
494                        expected: PropertyType::$variant,
495                        received: value.get_type(),
496                    }),
497                }
498            }
499        }
500    };
501}
502
503tryfrom_impl_propvalref!(bool, Bool);
504tryfrom_impl_propvalref!(i64, Int);
505tryfrom_impl_propvalref!(f64, Float);
506tryfrom_impl_propvalref!(u64, Unsigned);
507tryfrom_impl_propvalref!(Duration, Duration);
508tryfrom_impl_propvalref!(String, EnumStr);
509tryfrom_impl_propvalref!(GenCamPixelBpp, PixelFmt);
510
511impl From<&PropertyValue> for PropertyType {
512    fn from(prop: &PropertyValue) -> Self {
513        use PropertyValue::*;
514        match prop {
515            Command => PropertyType::Command,
516            Bool(_) => PropertyType::Bool,
517            Int(_) => PropertyType::Int,
518            Float(_) => PropertyType::Float,
519            Unsigned(_) => PropertyType::Unsigned,
520            PixelFmt(_) => PropertyType::PixelFmt,
521            Duration(_) => PropertyType::Duration,
522            EnumStr(_) => PropertyType::EnumStr,
523        }
524    }
525}
526
527impl PropertyValue {
528    /// Get the value as a boolean
529    pub fn as_bool(&self) -> Option<bool> {
530        match self {
531            PropertyValue::Bool(val) => Some(*val),
532            _ => None,
533        }
534    }
535    /// Get the value as an integer
536    pub fn as_i64(&self) -> Option<i64> {
537        match self {
538            PropertyValue::Int(val) => Some(*val),
539            _ => None,
540        }
541    }
542    /// Get the value as a floating point number
543    pub fn as_f64(&self) -> Option<f64> {
544        match self {
545            PropertyValue::Float(val) => Some(*val),
546            _ => None,
547        }
548    }
549    /// Get the value as an unsigned integer
550    pub fn as_u64(&self) -> Option<u64> {
551        match self {
552            PropertyValue::Unsigned(val) => Some(*val),
553            _ => None,
554        }
555    }
556    /// Get the value as a duration
557    pub fn as_duration(&self) -> Option<Duration> {
558        match self {
559            PropertyValue::Duration(val) => Some(*val),
560            _ => None,
561        }
562    }
563    /// Get the value as a pixel format
564    pub fn as_pixel_fmt(&self) -> Option<GenCamPixelBpp> {
565        match self {
566            PropertyValue::PixelFmt(val) => Some(*val),
567            _ => None,
568        }
569    }
570    /// Get the value as an enum string
571    pub fn as_enum_str(&self) -> Option<&str> {
572        match self {
573            PropertyValue::EnumStr(val) => Some(val),
574            _ => None,
575        }
576    }
577}
578
579#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
580#[non_exhaustive]
581/// The type of a property
582pub enum PropertyType {
583    /// A command property
584    Command,
585    /// A boolean property
586    Bool,
587    /// An integer property ([`i64`])
588    Int,
589    /// A floating point property ([`f64`])
590    Float,
591    /// An unsigned integer property ([`u64`])
592    Unsigned,
593    /// A pixel format property ([`GenCamPixelBpp`])
594    PixelFmt,
595    /// A duration property ([`Duration`])
596    Duration,
597    /// An enum string property ([`String`])
598    EnumStr,
599    /// An enum integer property ([`i64`])
600    EnumInt,
601    /// An enum unsigned integer property ([`u64`])
602    EnumUnsigned,
603}
604
605impl From<&PropertyLims> for PropertyType {
606    fn from(prop: &PropertyLims) -> Self {
607        use PropertyLims::*;
608        match prop {
609            Bool { .. } => PropertyType::Bool,
610            Int { .. } => PropertyType::Int,
611            Float { .. } => PropertyType::Float,
612            Unsigned { .. } => PropertyType::Unsigned,
613            Duration { .. } => PropertyType::Duration,
614            PixelFmt { .. } => PropertyType::PixelFmt,
615            EnumStr { .. } => PropertyType::EnumStr,
616            EnumInt { .. } => PropertyType::EnumInt,
617            EnumUnsigned { .. } => PropertyType::EnumUnsigned,
618        }
619    }
620}
621
622#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
623/// Property value error
624pub enum PropertyError {
625    /// Property not found.
626    #[error("Property not found")]
627    NotFound,
628    /// Read only property.
629    #[error("Property is read only")]
630    ReadOnly,
631    /// Property not an enum.
632    #[error("Property is not an enum")]
633    NotEnum,
634    /// Property is not a number.
635    #[error("Property is not a number")]
636    NotNumber,
637    #[error("Value out of range")]
638    /// Value out of range.
639    ValueOutOfRange {
640        /// The minimum value.
641        min: PropertyValue,
642        /// The maximum value.
643        max: PropertyValue,
644        /// The supplied value.
645        value: PropertyValue,
646    },
647    #[error("Value not supported")]
648    /// Value not contained in the enum list.
649    ValueNotSupported,
650    /// Property is an enum, hence does not support min/max.
651    #[error("Property is an enum")]
652    IsEnum,
653    /// Auto mode not supported.
654    #[error("Auto mode not supported")]
655    AutoNotSupported,
656    #[error("Invalid control type: {expected:?} != {received:?}")]
657    /// Invalid control type.
658    InvalidControlType {
659        /// The expected type.
660        expected: PropertyType,
661        /// The received type.
662        received: PropertyType,
663    },
664    #[error("Empty enum list")]
665    /// Empty enum list.
666    EmptyEnumList,
667}