Skip to main content

std_rs/device_support/
time_of_day.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3use epics_base_rs::error::CaResult;
4use epics_base_rs::server::device_support::{DeviceReadOutcome, DeviceSupport};
5use epics_base_rs::server::record::Record;
6use epics_base_rs::types::EpicsValue;
7
8use chrono::Local;
9
10/// EPICS epoch offset: seconds from Unix epoch (1970-01-01) to EPICS epoch (1990-01-01).
11const EPICS_EPOCH_OFFSET: u64 = 631152000;
12
13/// "Time of Day" device support for stringin records.
14///
15/// Reads the current time and formats it as a string.
16/// Format depends on PHAS field:
17/// - PHAS=0: "Mon DD, YYYY HH:MM:SS"
18/// - PHAS!=0: "MM/DD/YY HH:MM:SS"
19///
20/// Ported from `devTimeOfDay.c` (`devSiTodString`).
21pub struct TimeOfDayStringDeviceSupport;
22
23impl Default for TimeOfDayStringDeviceSupport {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl TimeOfDayStringDeviceSupport {
30    pub fn new() -> Self {
31        Self
32    }
33}
34
35impl DeviceSupport for TimeOfDayStringDeviceSupport {
36    fn dtyp(&self) -> &str {
37        "Time of Day"
38    }
39
40    fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
41        let now = Local::now();
42
43        // Check PHAS field to determine format (PHAS is managed by CommonFields,
44        // but we can read it via get_field if available; default to format 0)
45        let phas = record
46            .get_field("PHAS")
47            .and_then(|v| match v {
48                EpicsValue::Short(s) => Some(s),
49                _ => None,
50            })
51            .unwrap_or(0);
52
53        let formatted = if phas != 0 {
54            now.format("%m/%d/%y %H:%M:%S").to_string()
55        } else {
56            now.format("%b %d, %Y %H:%M:%S").to_string()
57        };
58
59        record.put_field("VAL", EpicsValue::String(formatted))?;
60        Ok(DeviceReadOutcome::computed())
61    }
62
63    fn write(&mut self, _record: &mut dyn Record) -> CaResult<()> {
64        Ok(())
65    }
66}
67
68/// "Sec Past Epoch" device support for ai records.
69///
70/// Reads the current time as seconds past the EPICS epoch (1990-01-01).
71/// If PHAS field is nonzero, includes fractional seconds.
72///
73/// Ported from `devTimeOfDay.c` (`devAiTodSeconds`).
74pub struct SecPastEpochDeviceSupport;
75
76impl Default for SecPastEpochDeviceSupport {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl SecPastEpochDeviceSupport {
83    pub fn new() -> Self {
84        Self
85    }
86}
87
88impl DeviceSupport for SecPastEpochDeviceSupport {
89    fn dtyp(&self) -> &str {
90        "Sec Past Epoch"
91    }
92
93    fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
94        let now = SystemTime::now()
95            .duration_since(UNIX_EPOCH)
96            .unwrap_or_default();
97
98        let sec_past_epoch = now.as_secs().saturating_sub(EPICS_EPOCH_OFFSET);
99
100        let phas = record
101            .get_field("PHAS")
102            .and_then(|v| match v {
103                EpicsValue::Short(s) => Some(s),
104                _ => None,
105            })
106            .unwrap_or(0);
107
108        let val = if phas != 0 {
109            sec_past_epoch as f64 + (now.subsec_nanos() as f64 / 1e9)
110        } else {
111            sec_past_epoch as f64
112        };
113
114        record.put_field("VAL", EpicsValue::Double(val))?;
115        Ok(DeviceReadOutcome::computed())
116    }
117
118    fn write(&mut self, _record: &mut dyn Record) -> CaResult<()> {
119        Ok(())
120    }
121}