indi/serialization/
number_vector.rs

1use std::fmt;
2use std::str;
3
4use super::super::*;
5use super::*;
6
7impl Sexagesimal {
8    fn format_indi_style(&self, precision: usize) -> String {
9        let is_negative = self.hour < 0.0;
10        let hour = self.hour.abs();
11
12        // Get minute value, defaulting to fractional part of hour if None
13        let minute = self.minute.unwrap_or_else(|| (hour - hour.trunc()) * 60.0);
14
15        // Get second value, defaulting to fractional part of minute if None
16        let second = self
17            .second
18            .unwrap_or_else(|| (minute - minute.trunc()) * 60.0);
19
20        // Format based on precision
21        let result = match precision {
22            9 => format!(
23                "{:02}:{:02}.{:02}",
24                minute.trunc() as i64,
25                second.trunc() as i64,
26                ((second % 1.0) * 100.0).round() as i64
27            ),
28            8 => format!(
29                "{:02}:{:02}.{:01}",
30                minute.trunc() as i64,
31                second.trunc() as i64,
32                ((second % 1.0) * 10.0).round() as i64
33            ),
34            6 => format!("{:02}:{:02}", minute.trunc() as i64, second.round() as i64),
35            5 => format!(
36                "{:02}.{:01}",
37                minute.trunc() as i64,
38                ((minute % 1.0) * 10.0).round() as i64
39            ),
40            3 => format!("{:02}", minute.round() as i64),
41            _ => format!("{:02}:{:02}", minute.trunc() as i64, second.round() as i64),
42        };
43
44        // Add the hours and sign
45        let mut final_result = String::new();
46        if is_negative {
47            final_result.push('-');
48        }
49        if hour >= 1.0 || hour.trunc() != 0.0 {
50            final_result.push_str(&format!("{}:", hour.trunc() as i64));
51        }
52        final_result.push_str(&result);
53
54        final_result
55    }
56
57    fn format_double(&self) -> f64 {
58        let mut value = self.hour;
59        if let Some(min) = self.minute {
60            value += min / 60.0;
61            if let Some(sec) = self.second {
62                value += sec / 3600.0;
63            }
64        }
65        value
66    }
67}
68
69fn parse_m_format(format: &str) -> Option<usize> {
70    // Look for pattern %.{n}m where n is the precision
71    let parts: Vec<&str> = format.split('.').collect();
72    if parts.len() != 2 {
73        return None;
74    }
75
76    if !parts[1].ends_with('m') {
77        return None;
78    }
79
80    // Extract the precision number
81    let precision_str = &parts[1][..parts[1].len() - 1];
82    precision_str.parse().ok()
83}
84
85impl fmt::Display for Number {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        // Check if this is an m-format
88        if let Some(m_precision) = parse_m_format(&self.format) {
89            // INDI sexagesimal format
90            let formatted = self.value.format_indi_style(m_precision);
91            // Handle width alignment if specified
92            if let Some(width) = f.width() {
93                if f.sign_aware_zero_pad() {
94                    write!(f, "{:0>width$}", formatted, width = width)
95                } else if f.align() == Some(fmt::Alignment::Left) {
96                    write!(f, "{:<width$}", formatted, width = width)
97                } else {
98                    write!(f, "{:>width$}", formatted, width = width)
99                }
100            } else {
101                write!(f, "{}", formatted)
102            }
103        } else {
104            // Regular double format
105            write!(f, "{}", self.value.format_double())
106        }
107    }
108}
109
110impl<'de> Deserialize<'de> for Sexagesimal {
111    fn deserialize<D>(deserializer: D) -> Result<Sexagesimal, D::Error>
112    where
113        D: serde::Deserializer<'de>,
114    {
115        let s: String = Deserialize::deserialize(deserializer)?;
116        let mut components = s.split([' ', ':']);
117
118        let hour = components
119            .next()
120            .map(str::parse)
121            .transpose()
122            .unwrap()
123            .unwrap();
124        let minute = components.next().map(str::parse).transpose().unwrap();
125        let second = components.next().map(str::parse).transpose().unwrap();
126
127        Ok(Sexagesimal {
128            hour,
129            minute,
130            second,
131        })
132    }
133}
134
135impl Serialize for Sexagesimal {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: serde::Serializer,
139    {
140        serializer.serialize_str(format!("{}", self).as_str())
141    }
142}
143
144impl std::fmt::Display for Sexagesimal {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        write!(f, "{}", self.hour)?;
147        if let Some(minute) = self.minute {
148            write!(f, ":{}", minute)?;
149        }
150        if let Some(second) = self.second {
151            write!(f, ":{}", second)?;
152        }
153
154        Ok(())
155    }
156}
157
158impl From<f64> for Sexagesimal {
159    fn from(value: f64) -> Self {
160        Self {
161            hour: value as f64,
162            minute: None,
163            second: None,
164        }
165    }
166}
167
168impl From<usize> for Sexagesimal {
169    fn from(value: usize) -> Self {
170        Self {
171            hour: value as f64,
172            minute: None,
173            second: None,
174        }
175    }
176}
177
178impl From<u64> for Sexagesimal {
179    fn from(value: u64) -> Self {
180        Self {
181            hour: value as f64,
182            minute: None,
183            second: None,
184        }
185    }
186}
187
188impl From<i32> for Sexagesimal {
189    fn from(value: i32) -> Self {
190        Self {
191            hour: value as f64,
192            minute: None,
193            second: None,
194        }
195    }
196}
197
198impl From<u32> for Sexagesimal {
199    fn from(value: u32) -> Self {
200        Self {
201            hour: value as f64,
202            minute: None,
203            second: None,
204        }
205    }
206}
207
208impl From<u16> for Sexagesimal {
209    fn from(value: u16) -> Self {
210        Self {
211            hour: value as f64,
212            minute: None,
213            second: None,
214        }
215    }
216}
217
218impl From<u8> for Sexagesimal {
219    fn from(value: u8) -> Self {
220        Self {
221            hour: value as f64,
222            minute: None,
223            second: None,
224        }
225    }
226}
227
228impl From<Sexagesimal> for f64 {
229    fn from(value: Sexagesimal) -> Self {
230        let mut val = value.hour;
231
232        let sign = value.hour.signum();
233        let div = 60.0;
234
235        if let Some(minute) = value.minute {
236            val += sign * minute / div;
237        }
238        if let Some(second) = value.second {
239            val += sign * second / (div * div);
240        }
241
242        val
243    }
244}
245
246impl CommandtoParam for DefNumberVector {
247    fn get_name(&self) -> &String {
248        &self.name
249    }
250    fn get_group(&self) -> &Option<String> {
251        &self.group
252    }
253    fn to_param(self) -> Parameter {
254        Parameter::NumberVector(NumberVector {
255            name: self.name,
256            group: self.group,
257            label: self.label,
258            state: self.state,
259            perm: self.perm,
260            timeout: self.timeout,
261            timestamp: self.timestamp.map(Timestamp::into_inner),
262            values: self
263                .numbers
264                .into_iter()
265                .map(|i| {
266                    (
267                        i.name,
268                        Number {
269                            label: i.label,
270                            format: i.format,
271                            min: i.min,
272                            max: i.max,
273                            step: i.step,
274                            value: i.value,
275                        },
276                    )
277                })
278                .collect(),
279        })
280    }
281}
282
283impl CommandToUpdate for SetNumberVector {
284    fn get_name(&self) -> &String {
285        &self.name
286    }
287
288    fn update_param(self, param: &mut Parameter) -> Result<String, UpdateError> {
289        match param {
290            Parameter::NumberVector(number_vector) => {
291                number_vector.state = self.state;
292                number_vector.timeout = self.timeout;
293                number_vector.timestamp = self.timestamp.map(Timestamp::into_inner);
294                for number in self.numbers {
295                    if let Some(existing) = number_vector.values.get_mut(&number.name) {
296                        existing.min = number.min.unwrap_or(existing.min);
297                        existing.max = number.max.unwrap_or(existing.max);
298                        existing.step = number.step.unwrap_or(existing.step);
299                        existing.value = number.value;
300                    }
301                }
302                Ok(self.name)
303            }
304            _ => Err(UpdateError::ParameterTypeMismatch(self.name.clone())),
305        }
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    // use std::io::Cursor;
313
314    #[test]
315    fn test_def_number() {
316        let xml = r#"
317        <defNumber name="SIM_XRES" label="CCD X resolution" format="%4.0f" min="512" max="8192" step="512">
318    1280
319        </defNumber>
320                    "#;
321        let command: Result<DefNumber, _> = quick_xml::de::from_str(xml);
322
323        match command {
324            Ok(param) => {
325                assert_eq!(param.name, "SIM_XRES");
326                assert_eq!(param.label, Some(String::from("CCD X resolution")));
327                assert_eq!(param.value, 1280.0.into());
328            }
329            Err(e) => {
330                panic!("Unexpected: {:?}", e);
331            }
332        }
333    }
334
335    #[test]
336    fn test_def_number_vector() {
337        let xml = r#"
338    <defNumberVector device="CCD Simulator" name="SIMULATOR_SETTINGS" label="Settings" group="Simulator Config" state="Idle" perm="rw" timeout="60" timestamp="2022-08-12T05:52:27">
339        <defNumber name="SIM_XRES" label="CCD X resolution" format="%4.0f" min="512" max="8192" step="512">
340    1280
341        </defNumber>
342        <defNumber name="SIM_YRES" label="CCD Y resolution" format="%4.0f" min="512" max="8192" step="512">
343    1024
344        </defNumber>
345        <defNumber name="SIM_XSIZE" label="CCD X Pixel Size" format="%4.2f" min="1" max="30" step="5">
346    5.2000000000000001776
347        </defNumber>
348    </defNumberVector>
349                    "#;
350        let command: Result<DefNumberVector, _> = quick_xml::de::from_str(xml);
351
352        match command {
353            Ok(param) => {
354                assert_eq!(param.device, "CCD Simulator");
355                assert_eq!(param.name, "SIMULATOR_SETTINGS");
356                assert_eq!(param.numbers.len(), 3)
357            }
358            e => {
359                panic!("Unexpected: {:?}", e)
360            }
361        }
362    }
363
364    #[test]
365    fn test_set_number_vector() {
366        let xml = r#"
367    <setNumberVector device="CCD Simulator" name="SIM_FOCUSING" state="Ok" timeout="60" timestamp="2022-10-01T21:21:10">
368    <oneNumber name="SIM_FOCUS_POSITION">
369    7340
370    </oneNumber>
371    <oneNumber name="SIM_FOCUS_MAX">
372    100000
373    </oneNumber>
374    <oneNumber name="SIM_SEEING">
375    3.5
376    </oneNumber>
377    </setNumberVector>
378"#;
379
380        let command: Result<SetNumberVector, _> = quick_xml::de::from_str(xml);
381
382        match command {
383            Ok(param) => {
384                assert_eq!(param.device, "CCD Simulator");
385                assert_eq!(param.name, "SIM_FOCUSING");
386                assert_eq!(param.numbers.len(), 3)
387            }
388            e => {
389                panic!("Unexpected: {:?}", e)
390            }
391        }
392    }
393
394    #[test]
395    fn test_new_number_vector() {
396        let xml = r#"
397    <newNumberVector device="CCD Simulator" name="SIM_FOCUSING" timestamp="2022-10-01T21:21:10">
398    <oneNumber name="SIM_FOCUS_POSITION">
399    7340
400    </oneNumber>
401    <oneNumber name="SIM_FOCUS_MAX">
402    100000
403    </oneNumber>
404    <oneNumber name="SIM_SEEING">
405    3.5
406    </oneNumber>
407    </newNumberVector>
408    "#;
409
410        let command: Result<NewNumberVector, _> = quick_xml::de::from_str(xml);
411
412        match command {
413            Ok(param) => {
414                assert_eq!(param.device, "CCD Simulator");
415                assert_eq!(param.name, "SIM_FOCUSING");
416                assert_eq!(param.numbers.len(), 3)
417            }
418            e => {
419                panic!("Unexpected: {:?}", e)
420            }
421        }
422    }
423
424    #[test]
425    fn test_parse_number_normal() {
426        let xml = r#"-10.505"#;
427
428        let event: Result<Sexagesimal, _> = quick_xml::de::from_str(xml);
429
430        if let Ok(e) = event {
431            assert_eq!(Into::<Sexagesimal>::into(-10.505), e);
432        } else {
433            panic!("Unexpected");
434        }
435    }
436
437    #[test]
438    fn test_parse_number_sexagesimal_1() {
439        let xml = r#"-10 30.3"#;
440
441        let event: Result<Sexagesimal, _> = quick_xml::de::from_str(xml);
442
443        if let Ok(e) = event {
444            assert_eq!(
445                Sexagesimal {
446                    hour: -10.,
447                    minute: Some(30.3),
448                    second: None
449                },
450                e.into()
451            );
452        } else {
453            panic!("Unexpected");
454        }
455    }
456
457    #[test]
458    fn test_parse_number_sexagesimal_2() {
459        let xml = r#"-10:30:18"#;
460
461        let event: Result<Sexagesimal, _> = quick_xml::de::from_str(xml);
462
463        if let Ok(e) = event {
464            assert_eq!(
465                Sexagesimal {
466                    hour: -10.0,
467                    minute: Some(30.),
468                    second: Some(18.)
469                },
470                e.into()
471            );
472        } else {
473            panic!("Unexpected");
474        }
475    }
476
477    #[test]
478    fn test_send_new_number_vector() {
479        let timestamp = DateTime::from_str("2022-10-13T07:41:56.301Z")
480            .unwrap()
481            .into();
482
483        let command = NewNumberVector {
484            device: String::from_str("CCD Simulator").unwrap(),
485            name: String::from_str("Exposure").unwrap(),
486            timestamp: Some(timestamp),
487            numbers: vec![OneNumber {
488                name: String::from_str("seconds").unwrap(),
489                value: 3.0.into(),
490            }],
491        };
492
493        // command.write(&mut writer).unwrap();
494
495        // let result = writer.into_inner().into_inner();
496        let result = quick_xml::se::to_string(&command).unwrap();
497        assert_eq!(
498            result,
499            String::from_str("<newNumberVector device=\"CCD Simulator\" name=\"Exposure\" timestamp=\"2022-10-13T07:41:56.301\"><oneNumber name=\"seconds\">3</oneNumber></newNumberVector>").unwrap()
500        );
501    }
502}