use std::time::Duration;
use crate::GenCamPixelBpp;
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub type PropertyResult<T> = Result<T, PropertyError>;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Property {
auto: bool,
rdonly: bool,
prop: PropertyLims,
doc: Option<String>,
}
impl Property {
pub fn new(prop: PropertyLims, auto_supported: bool, rdonly: bool) -> Self {
Property {
auto: auto_supported,
rdonly,
prop,
doc: None,
}
}
pub fn set_doc<T: Into<String>>(&mut self, doc: T) {
self.doc = Some(doc.into());
}
pub fn get_doc(&self) -> Option<&str> {
self.doc.as_deref()
}
pub fn get_type(&self) -> PropertyType {
(&self.prop).into()
}
pub fn supports_auto(&self) -> bool {
self.auto
}
pub fn validate(&self, value: &PropertyValue) -> PropertyResult<()> {
match self.prop {
PropertyLims::EnumStr { ref variants, .. } => {
if let PropertyValue::EnumStr(ref val) = value {
if variants.contains(val) {
return Ok(());
} else {
return Err(PropertyError::ValueNotSupported);
}
} else {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::EnumStr,
received: value.get_type(),
});
}
}
PropertyLims::EnumInt { ref variants, .. } => {
if let PropertyValue::Int(ref val) = value {
if variants.contains(val) {
return Ok(());
} else {
return Err(PropertyError::ValueNotSupported);
}
} else {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::EnumInt,
received: value.get_type(),
});
}
}
PropertyLims::EnumUnsigned { ref variants, .. } => {
if let PropertyValue::Unsigned(ref val) = value {
if variants.contains(val) {
return Ok(());
} else {
return Err(PropertyError::ValueNotSupported);
}
} else {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::EnumUnsigned,
received: value.get_type(),
});
}
}
PropertyLims::Duration { .. } => {
if value.get_type() != PropertyType::Duration {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::Duration,
received: value.get_type(),
});
}
}
PropertyLims::Bool { .. } => {
if value.get_type() != PropertyType::Bool {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::Bool,
received: value.get_type(),
});
}
}
PropertyLims::Int { .. } => {
if value.get_type() != PropertyType::Int {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::Int,
received: value.get_type(),
});
}
}
PropertyLims::Float { .. } => {
if value.get_type() != PropertyType::Float {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::Float,
received: value.get_type(),
});
}
}
PropertyLims::Unsigned { .. } => {
if value.get_type() != PropertyType::Unsigned {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::Unsigned,
received: value.get_type(),
});
}
}
PropertyLims::PixelFmt { .. } => {
if value.get_type() != PropertyType::PixelFmt {
return Err(PropertyError::InvalidControlType {
expected: PropertyType::PixelFmt,
received: value.get_type(),
});
}
}
}
match self.get_type() {
PropertyType::Int
| PropertyType::Unsigned
| PropertyType::Float
| PropertyType::Duration => {
if &self.get_min()? <= value && value <= &self.get_max()? {
Ok(())
} else {
Err(PropertyError::ValueOutOfRange {
value: value.clone(),
min: self.get_min().unwrap(), max: self.get_max().unwrap(), })
}
}
PropertyType::Bool => Ok(()),
PropertyType::Command
| PropertyType::PixelFmt
| PropertyType::EnumStr
| PropertyType::EnumInt
| PropertyType::EnumUnsigned => Err(PropertyError::NotNumber),
}
}
pub fn get_min(&self) -> PropertyResult<PropertyValue> {
use PropertyLims::*;
match &self.prop {
Bool { .. } => Err(PropertyError::NotNumber),
Int { min, .. } => Ok((*min).into()),
Float { min, .. } => Ok((*min).into()),
Unsigned { min, .. } => Ok((*min).into()),
Duration { min, .. } => Ok((*min).into()),
PixelFmt { variants, .. } => {
Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
}
EnumStr { .. } => Err(PropertyError::NotNumber),
EnumInt { variants, .. } => {
Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
}
EnumUnsigned { variants, .. } => {
Ok((*variants.iter().min().ok_or(PropertyError::EmptyEnumList)?).into())
}
}
}
pub fn get_max(&self) -> PropertyResult<PropertyValue> {
use PropertyLims::*;
match &self.prop {
Bool { .. } => Err(PropertyError::NotNumber),
Int { max, .. } => Ok((*max).into()),
Float { max, .. } => Ok((*max).into()),
Unsigned { max, .. } => Ok((*max).into()),
Duration { max, .. } => Ok((*max).into()),
PixelFmt { variants, .. } => {
Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
}
EnumStr { .. } => Err(PropertyError::NotNumber),
EnumInt { variants, .. } => {
Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
}
EnumUnsigned { variants, .. } => {
Ok((*variants.iter().max().ok_or(PropertyError::EmptyEnumList)?).into())
}
}
}
pub fn get_step(&self) -> PropertyResult<PropertyValue> {
use PropertyLims::*;
match &self.prop {
Bool { .. } => Err(PropertyError::NotNumber),
Int { step, .. } => Ok((*step).into()),
Float { step, .. } => Ok((*step).into()),
Unsigned { step, .. } => Ok((*step).into()),
Duration { step, .. } => Ok((*step).into()),
PixelFmt { .. } => Err(PropertyError::IsEnum),
EnumStr { .. } => Err(PropertyError::NotNumber),
EnumInt { .. } => Err(PropertyError::IsEnum),
EnumUnsigned { .. } => Err(PropertyError::IsEnum),
}
}
pub fn get_default(&self) -> PropertyResult<PropertyValue> {
use PropertyLims::*;
match self.prop.clone() {
Bool { default } => Ok(default.into()),
Int { default, .. } => Ok(default.into()),
Float { default, .. } => Ok(default.into()),
Unsigned { default, .. } => Ok(default.into()),
Duration { default, .. } => Ok(default.into()),
PixelFmt { default, .. } => Ok(default.into()),
EnumStr { default, .. } => Ok(default.into()),
EnumInt { default, .. } => Ok(default.into()),
EnumUnsigned { default, .. } => Ok(default.into()),
}
}
pub fn get_variants(&self) -> PropertyResult<Vec<PropertyValue>> {
use PropertyLims::*;
match &self.prop {
Bool { .. } | Int { .. } | Float { .. } | Unsigned { .. } | Duration { .. } => {
Err(PropertyError::NotEnum)
}
PixelFmt { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
EnumStr { variants, .. } => Ok(variants.iter().map(|x| x.clone().into()).collect()),
EnumInt { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
EnumUnsigned { variants, .. } => Ok(variants.iter().map(|x| (*x).into()).collect()),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PropertyLims {
Bool {
default: bool,
},
Int {
min: i64,
max: i64,
step: i64,
default: i64,
},
Float {
min: f64,
max: f64,
step: f64,
default: f64,
},
Unsigned {
min: u64,
max: u64,
step: u64,
default: u64,
},
Duration {
min: Duration,
max: Duration,
step: Duration,
default: Duration,
},
PixelFmt {
variants: Vec<GenCamPixelBpp>,
default: GenCamPixelBpp,
},
EnumStr {
variants: Vec<String>,
default: String,
},
EnumInt {
variants: Vec<i64>,
default: i64,
},
EnumUnsigned {
variants: Vec<u64>,
default: u64,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, PartialOrd)]
#[non_exhaustive]
pub enum PropertyValue {
Command,
Bool(bool),
Int(i64),
Float(f64),
Unsigned(u64),
PixelFmt(GenCamPixelBpp),
Duration(Duration),
EnumStr(String),
}
impl PropertyValue {
pub fn get_type(&self) -> PropertyType {
self.into()
}
}
impl From<()> for PropertyValue {
fn from(_: ()) -> Self {
PropertyValue::Command
}
}
impl From<i64> for PropertyValue {
fn from(val: i64) -> Self {
PropertyValue::Int(val)
}
}
impl From<u64> for PropertyValue {
fn from(val: u64) -> Self {
PropertyValue::Unsigned(val)
}
}
impl From<f64> for PropertyValue {
fn from(val: f64) -> Self {
PropertyValue::Float(val)
}
}
impl From<Duration> for PropertyValue {
fn from(val: Duration) -> Self {
PropertyValue::Duration(val)
}
}
impl From<String> for PropertyValue {
fn from(val: String) -> Self {
PropertyValue::EnumStr(val)
}
}
impl From<&str> for PropertyValue {
fn from(val: &str) -> Self {
PropertyValue::EnumStr(val.to_owned())
}
}
impl From<bool> for PropertyValue {
fn from(val: bool) -> Self {
PropertyValue::Bool(val)
}
}
impl From<GenCamPixelBpp> for PropertyValue {
fn from(val: GenCamPixelBpp) -> Self {
PropertyValue::PixelFmt(val)
}
}
impl TryFrom<PropertyValue> for () {
type Error = PropertyError;
fn try_from(value: PropertyValue) -> Result<Self, Self::Error> {
match value {
PropertyValue::Command => Ok(()),
_ => Err(PropertyError::InvalidControlType {
expected: PropertyType::Command,
received: value.get_type(),
}),
}
}
}
impl TryFrom<&PropertyValue> for () {
type Error = PropertyError;
fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
match value {
PropertyValue::Command => Ok(()),
_ => Err(PropertyError::InvalidControlType {
expected: PropertyType::Command,
received: value.get_type(),
}),
}
}
}
macro_rules! tryfrom_impl_propval {
($type:ty, $variant:ident) => {
impl TryFrom<PropertyValue> for $type {
type Error = PropertyError;
fn try_from(value: PropertyValue) -> Result<Self, Self::Error> {
match value {
PropertyValue::$variant(val) => Ok(val),
_ => Err(PropertyError::InvalidControlType {
expected: PropertyType::$variant,
received: value.get_type(),
}),
}
}
}
};
}
tryfrom_impl_propval!(bool, Bool);
tryfrom_impl_propval!(i64, Int);
tryfrom_impl_propval!(f64, Float);
tryfrom_impl_propval!(u64, Unsigned);
tryfrom_impl_propval!(Duration, Duration);
tryfrom_impl_propval!(String, EnumStr);
tryfrom_impl_propval!(GenCamPixelBpp, PixelFmt);
macro_rules! tryfrom_impl_propvalref {
($type:ty, $variant:ident) => {
impl TryFrom<&PropertyValue> for $type {
type Error = PropertyError;
fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
match value {
PropertyValue::$variant(val) => Ok(val.clone()),
_ => Err(PropertyError::InvalidControlType {
expected: PropertyType::$variant,
received: value.get_type(),
}),
}
}
}
};
}
tryfrom_impl_propvalref!(bool, Bool);
tryfrom_impl_propvalref!(i64, Int);
tryfrom_impl_propvalref!(f64, Float);
tryfrom_impl_propvalref!(u64, Unsigned);
tryfrom_impl_propvalref!(Duration, Duration);
tryfrom_impl_propvalref!(String, EnumStr);
tryfrom_impl_propvalref!(GenCamPixelBpp, PixelFmt);
impl From<&PropertyValue> for PropertyType {
fn from(prop: &PropertyValue) -> Self {
use PropertyValue::*;
match prop {
Command => PropertyType::Command,
Bool(_) => PropertyType::Bool,
Int(_) => PropertyType::Int,
Float(_) => PropertyType::Float,
Unsigned(_) => PropertyType::Unsigned,
PixelFmt(_) => PropertyType::PixelFmt,
Duration(_) => PropertyType::Duration,
EnumStr(_) => PropertyType::EnumStr,
}
}
}
impl PropertyValue {
pub fn as_bool(&self) -> Option<bool> {
match self {
PropertyValue::Bool(val) => Some(*val),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
PropertyValue::Int(val) => Some(*val),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
PropertyValue::Float(val) => Some(*val),
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
PropertyValue::Unsigned(val) => Some(*val),
_ => None,
}
}
pub fn as_duration(&self) -> Option<Duration> {
match self {
PropertyValue::Duration(val) => Some(*val),
_ => None,
}
}
pub fn as_pixel_fmt(&self) -> Option<GenCamPixelBpp> {
match self {
PropertyValue::PixelFmt(val) => Some(*val),
_ => None,
}
}
pub fn as_enum_str(&self) -> Option<&str> {
match self {
PropertyValue::EnumStr(val) => Some(val),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PropertyType {
Command,
Bool,
Int,
Float,
Unsigned,
PixelFmt,
Duration,
EnumStr,
EnumInt,
EnumUnsigned,
}
impl From<&PropertyLims> for PropertyType {
fn from(prop: &PropertyLims) -> Self {
use PropertyLims::*;
match prop {
Bool { .. } => PropertyType::Bool,
Int { .. } => PropertyType::Int,
Float { .. } => PropertyType::Float,
Unsigned { .. } => PropertyType::Unsigned,
Duration { .. } => PropertyType::Duration,
PixelFmt { .. } => PropertyType::PixelFmt,
EnumStr { .. } => PropertyType::EnumStr,
EnumInt { .. } => PropertyType::EnumInt,
EnumUnsigned { .. } => PropertyType::EnumUnsigned,
}
}
}
#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PropertyError {
#[error("Property not found")]
NotFound,
#[error("Property is read only")]
ReadOnly,
#[error("Property is not an enum")]
NotEnum,
#[error("Property is not a number")]
NotNumber,
#[error("Value out of range")]
ValueOutOfRange {
min: PropertyValue,
max: PropertyValue,
value: PropertyValue,
},
#[error("Value not supported")]
ValueNotSupported,
#[error("Property is an enum")]
IsEnum,
#[error("Auto mode not supported")]
AutoNotSupported,
#[error("Invalid control type: {expected:?} != {received:?}")]
InvalidControlType {
expected: PropertyType,
received: PropertyType,
},
#[error("Empty enum list")]
EmptyEnumList,
}