use super::{FloatRange, xml_escape, xml_str};
use crate::error::OnvifError;
use crate::soap::{SoapError, XmlNode};
#[derive(Debug, Clone, Default)]
pub struct ImagingSettings {
pub brightness: Option<f32>,
pub color_saturation: Option<f32>,
pub contrast: Option<f32>,
pub sharpness: Option<f32>,
pub ir_cut_filter: Option<String>,
pub white_balance_mode: Option<String>,
pub exposure_mode: Option<String>,
}
impl ImagingSettings {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let s = resp
.child("ImagingSettings")
.ok_or_else(|| SoapError::missing("ImagingSettings"))?;
let parse_f32 = |child: &str| s.child(child).and_then(|n| n.text().parse::<f32>().ok());
Ok(Self {
brightness: parse_f32("Brightness"),
color_saturation: parse_f32("ColorSaturation"),
contrast: parse_f32("Contrast"),
sharpness: parse_f32("Sharpness"),
ir_cut_filter: xml_str(s, "IrCutFilter").filter(|v| !v.is_empty()),
white_balance_mode: s
.path(&["WhiteBalance", "Mode"])
.map(|n| n.text().to_string())
.filter(|v| !v.is_empty()),
exposure_mode: s
.path(&["Exposure", "Mode"])
.map(|n| n.text().to_string())
.filter(|v| !v.is_empty()),
})
}
pub(crate) fn to_xml_body(&self) -> String {
let mut out = String::from("<timg:ImagingSettings>");
if let Some(v) = self.brightness {
out.push_str(&format!("<tt:Brightness>{v}</tt:Brightness>"));
}
if let Some(v) = self.color_saturation {
out.push_str(&format!("<tt:ColorSaturation>{v}</tt:ColorSaturation>"));
}
if let Some(v) = self.contrast {
out.push_str(&format!("<tt:Contrast>{v}</tt:Contrast>"));
}
if let Some(v) = self.sharpness {
out.push_str(&format!("<tt:Sharpness>{v}</tt:Sharpness>"));
}
if let Some(ref v) = self.ir_cut_filter {
out.push_str(&format!(
"<tt:IrCutFilter>{}</tt:IrCutFilter>",
xml_escape(v)
));
}
if let Some(ref m) = self.white_balance_mode {
out.push_str(&format!(
"<tt:WhiteBalance><tt:Mode>{}</tt:Mode></tt:WhiteBalance>",
xml_escape(m)
));
}
if let Some(ref m) = self.exposure_mode {
out.push_str(&format!(
"<tt:Exposure><tt:Mode>{}</tt:Mode></tt:Exposure>",
xml_escape(m)
));
}
out.push_str("</timg:ImagingSettings>");
out
}
}
#[derive(Debug, Clone, Default)]
pub struct ImagingOptions {
pub brightness: Option<FloatRange>,
pub color_saturation: Option<FloatRange>,
pub contrast: Option<FloatRange>,
pub sharpness: Option<FloatRange>,
pub ir_cut_filter_modes: Vec<String>,
pub white_balance_modes: Vec<String>,
pub exposure_modes: Vec<String>,
}
impl ImagingOptions {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let opts = resp
.child("ImagingOptions")
.ok_or_else(|| SoapError::missing("ImagingOptions"))?;
let parse_range = |child: &str| {
opts.child(child).map(|n| FloatRange {
min: n
.child("Min")
.and_then(|m| m.text().parse().ok())
.unwrap_or(0.0),
max: n
.child("Max")
.and_then(|m| m.text().parse().ok())
.unwrap_or(0.0),
})
};
Ok(Self {
brightness: parse_range("Brightness"),
color_saturation: parse_range("ColorSaturation"),
contrast: parse_range("Contrast"),
sharpness: parse_range("Sharpness"),
ir_cut_filter_modes: opts
.children_named("IrCutFilterModes")
.map(|n| n.text().to_string())
.collect(),
white_balance_modes: opts
.child("WhiteBalance")
.map(|wb| {
wb.children_named("Mode")
.map(|n| n.text().to_string())
.collect()
})
.unwrap_or_default(),
exposure_modes: opts
.child("Exposure")
.map(|e| {
e.children_named("Mode")
.map(|n| n.text().to_string())
.collect()
})
.unwrap_or_default(),
})
}
}
#[derive(Debug, Clone, Default)]
pub struct ImagingStatus {
pub focus_position: Option<f32>,
pub focus_move_status: String,
}
impl ImagingStatus {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let status = resp
.child("Status")
.ok_or_else(|| SoapError::missing("Status"))?;
Ok(Self {
focus_position: status
.path(&["FocusStatus20", "Position"])
.and_then(|n| n.text().parse().ok()),
focus_move_status: status
.path(&["FocusStatus20", "MoveStatus"])
.map(|n| n.text().to_string())
.unwrap_or_else(|| "UNKNOWN".to_string()),
})
}
}
#[derive(Debug, Clone, Default)]
pub struct ImagingMoveOptions {
pub absolute_position_range: Option<FloatRange>,
pub absolute_speed_range: Option<FloatRange>,
pub relative_distance_range: Option<FloatRange>,
pub relative_speed_range: Option<FloatRange>,
pub continuous_speed_range: Option<FloatRange>,
}
impl ImagingMoveOptions {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let opts = resp
.child("MoveOptions")
.ok_or_else(|| SoapError::missing("MoveOptions"))?;
let range = |parent: &str, child: &str| {
opts.child(parent)
.and_then(|p| p.child(child))
.map(|n| FloatRange {
min: n
.child("Min")
.and_then(|m| m.text().parse().ok())
.unwrap_or(0.0),
max: n
.child("Max")
.and_then(|m| m.text().parse().ok())
.unwrap_or(0.0),
})
};
Ok(Self {
absolute_position_range: range("Absolute", "PositionSpace"),
absolute_speed_range: range("Absolute", "SpeedSpace"),
relative_distance_range: range("Relative", "DistanceSpace"),
relative_speed_range: range("Relative", "SpeedSpace"),
continuous_speed_range: range("Continuous", "SpeedSpace"),
})
}
}
#[derive(Debug, Clone)]
pub enum FocusMove {
Absolute {
position: f32,
speed: Option<f32>,
},
Relative {
distance: f32,
speed: Option<f32>,
},
Continuous {
speed: f32,
},
}
impl FocusMove {
pub(crate) fn to_xml_body(&self) -> String {
match self {
Self::Absolute { position, speed } => {
let speed_el = speed
.map(|s| format!("<timg:Speed>{s}</timg:Speed>"))
.unwrap_or_default();
format!(
"<timg:Absolute>\
<timg:Position>{position}</timg:Position>\
{speed_el}\
</timg:Absolute>"
)
}
Self::Relative { distance, speed } => {
let speed_el = speed
.map(|s| format!("<timg:Speed>{s}</timg:Speed>"))
.unwrap_or_default();
format!(
"<timg:Relative>\
<timg:Distance>{distance}</timg:Distance>\
{speed_el}\
</timg:Relative>"
)
}
Self::Continuous { speed } => {
format!("<timg:Continuous><timg:Speed>{speed}</timg:Speed></timg:Continuous>")
}
}
}
}