ados_protocol/
camera_control.rs1use rmpv::Value;
26use serde::{Deserialize, Serialize};
27
28pub const CAMERA_CONTROL_METHOD: &str = "camera.control.set";
30pub const CAMERA_CONTROL_CAPABILITY: &str = "camera.control.write";
32pub const ENGINE_WAIT_MS: u64 = 1_500;
36
37pub mod reason {
39 pub const NOT_V4L2: &str = "not_v4l2";
42 pub const CONTROL_UNSUPPORTED: &str = "control_unsupported";
44 pub const UNKNOWN_CAMERA: &str = "unknown_camera";
46 pub const NOT_CAPTURE_CAMERA: &str = "not_capture_camera";
49 pub const GAIN_UNIT_UNKNOWN: &str = "gain_unit_unknown";
52 pub const NOT_STREAMING: &str = "not_streaming";
55 pub const OUT_OF_RANGE: &str = "out_of_range";
57 pub const SET_FAILED: &str = "set_failed";
59 pub const READBACK_MISMATCH: &str = "readback_mismatch";
61 pub const CONFLICTING_REQUEST: &str = "conflicting_request";
63 pub const INVALID_VALUE: &str = "invalid_value";
65 pub const EMPTY_REQUEST: &str = "empty_request";
67 pub const BUSY: &str = "busy";
69}
70
71#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
75pub struct CameraControlRequest {
76 pub camera_id: String,
77 #[serde(default)]
78 pub exposure_us: Option<u32>,
79 #[serde(default)]
80 pub gain: Option<f32>,
81 #[serde(default)]
82 pub auto_exposure: Option<bool>,
83}
84
85#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
89pub struct CameraControlResult {
90 pub applied: bool,
91 #[serde(default)]
92 pub exposure_us: Option<u32>,
93 #[serde(default)]
94 pub gain: Option<f32>,
95 #[serde(default)]
96 pub reason: Option<String>,
97}
98
99impl CameraControlResult {
100 pub fn refused(reason: &str) -> Self {
102 Self {
103 applied: false,
104 exposure_us: None,
105 gain: None,
106 reason: Some(reason.to_string()),
107 }
108 }
109}
110
111fn decode<T: serde::de::DeserializeOwned>(value: &Value, what: &str) -> Result<T, String> {
113 rmpv::ext::from_value(value.clone()).map_err(|e| format!("{what}: {e}"))
114}
115
116fn map(entries: Vec<(&str, Option<Value>)>) -> Value {
117 Value::Map(
118 entries
119 .into_iter()
120 .filter_map(|(k, v)| v.map(|v| (Value::from(k), v)))
121 .collect(),
122 )
123}
124
125impl CameraControlRequest {
126 pub fn to_args(&self) -> Value {
128 map(vec![
129 ("camera_id", Some(Value::from(self.camera_id.as_str()))),
130 ("exposure_us", self.exposure_us.map(Value::from)),
131 ("gain", self.gain.map(Value::from)),
132 ("auto_exposure", self.auto_exposure.map(Value::from)),
133 ])
134 }
135
136 pub fn from_args(args: &Value) -> Result<Self, String> {
138 decode(args, "camera.control.set arguments are invalid")
139 }
140}
141
142impl CameraControlResult {
143 pub fn to_value(&self) -> Value {
145 map(vec![
146 ("applied", Some(Value::from(self.applied))),
147 ("exposure_us", self.exposure_us.map(Value::from)),
148 ("gain", self.gain.map(Value::from)),
149 ("reason", self.reason.as_deref().map(Value::from)),
150 ])
151 }
152
153 pub fn from_value(value: &Value) -> Result<Self, String> {
155 decode(value, "camera.control.set reply did not decode")
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn a_request_round_trips_and_omits_absent_fields() {
165 let req = CameraControlRequest {
166 camera_id: "cam_fl".into(),
167 exposure_us: Some(2_000),
168 gain: Some(4.0),
169 auto_exposure: None,
170 };
171 let args = req.to_args();
172 let Value::Map(entries) = &args else {
173 panic!("a map")
174 };
175 assert!(!entries
176 .iter()
177 .any(|(k, _)| k.as_str() == Some("auto_exposure")));
178 assert_eq!(CameraControlRequest::from_args(&args).unwrap(), req);
179 }
180
181 #[test]
182 fn a_request_without_optional_fields_decodes() {
183 let args = Value::Map(vec![(Value::from("camera_id"), Value::from("c"))]);
184 let req = CameraControlRequest::from_args(&args).unwrap();
185 assert_eq!(req.camera_id, "c");
186 assert_eq!(
187 (req.exposure_us, req.gain, req.auto_exposure),
188 (None, None, None)
189 );
190 let args = Value::Map(vec![
192 (Value::from("camera_id"), Value::from("c")),
193 (Value::from("gain"), Value::from(2)),
194 ]);
195 assert_eq!(
196 CameraControlRequest::from_args(&args).unwrap().gain,
197 Some(2.0)
198 );
199 assert!(CameraControlRequest::from_args(&Value::Map(vec![])).is_err());
200 }
201
202 #[test]
203 fn a_result_round_trips() {
204 let applied = CameraControlResult {
205 applied: true,
206 exposure_us: Some(2_000),
207 gain: Some(4.0),
208 reason: None,
209 };
210 assert_eq!(
211 CameraControlResult::from_value(&applied.to_value()).unwrap(),
212 applied
213 );
214 let refused = CameraControlResult::refused(reason::NOT_V4L2);
215 assert_eq!(
216 CameraControlResult::from_value(&refused.to_value()).unwrap(),
217 refused
218 );
219 }
220}