epics_base_rs/server/device_support.rs
1use crate::error::CaResult;
2use crate::server::record::{ProcessAction, Record, ScanType};
3
4/// Check if a DTYP string represents a soft/built-in device support
5/// that doesn't require an explicit device support registration.
6/// Matches C EPICS built-in soft device support names.
7pub fn is_soft_dtyp(dtyp: &str) -> bool {
8 dtyp.is_empty()
9 || dtyp == "Soft Channel"
10 || dtyp == "Raw Soft Channel"
11 || dtyp == "Async Soft Channel"
12 || dtyp == "Soft Timestamp"
13 || dtyp == "Sec Past Epoch"
14}
15
16/// Handle for waiting on asynchronous write completion.
17/// Returned by [`DeviceSupport::write_begin`] when the write is submitted
18/// to a worker queue rather than executed synchronously.
19pub trait WriteCompletion: Send + 'static {
20 /// Block until the write completes or timeout expires.
21 fn wait(&self, timeout: std::time::Duration) -> CaResult<()>;
22}
23
24/// Outcome of a device support read() call.
25///
26/// Allows device support to return side-effect actions (link writes,
27/// delayed reprocess) and signal that it has already performed the
28/// record's compute step (e.g., PID calculation).
29#[derive(Default)]
30pub struct DeviceReadOutcome {
31 /// Actions for the framework to execute (WriteDbLink, ReprocessAfter, etc.)
32 pub actions: Vec<ProcessAction>,
33 /// If true, the record's built-in compute (e.g., PID) was already
34 /// performed by device support. The record's process() should skip
35 /// its own computation. This replaces the `pid_done` flag pattern.
36 pub did_compute: bool,
37}
38
39impl DeviceReadOutcome {
40 /// Shorthand for a successful read with no actions.
41 pub fn ok() -> Self {
42 Self::default()
43 }
44
45 /// Shorthand for a read that performed the record's compute step.
46 pub fn computed() -> Self {
47 Self {
48 did_compute: true,
49 actions: Vec::new(),
50 }
51 }
52
53 /// Shorthand for a computed read with actions.
54 pub fn computed_with(actions: Vec<ProcessAction>) -> Self {
55 Self {
56 did_compute: true,
57 actions,
58 }
59 }
60}
61
62/// Trait for custom device support implementations.
63/// When DTYP is set to something other than "" or "Soft Channel",
64/// the registered DeviceSupport is used instead of link resolution.
65pub trait DeviceSupport: Send + Sync + 'static {
66 fn init(&mut self, _record: &mut dyn Record) -> CaResult<()> {
67 Ok(())
68 }
69
70 /// Read from hardware into the record.
71 ///
72 /// Returns a `DeviceReadOutcome` containing:
73 /// - `actions`: side-effect actions (link writes, delayed reprocess)
74 /// that the framework will execute after process()
75 /// - `did_compute`: if true, the record's built-in compute was already
76 /// performed (e.g., device support ran PID), so process() should skip it
77 fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
78 let _ = record;
79 Ok(DeviceReadOutcome::ok())
80 }
81
82 fn write(&mut self, record: &mut dyn Record) -> CaResult<()>;
83 fn dtyp(&self) -> &str;
84
85 /// Return the last alarm (status, severity) from the driver.
86 /// None means the driver does not override alarms.
87 fn last_alarm(&self) -> Option<(u16, u16)> {
88 None
89 }
90
91 /// Return the last timestamp from the driver.
92 /// None means the driver does not override timestamps.
93 fn last_timestamp(&self) -> Option<std::time::SystemTime> {
94 None
95 }
96
97 /// Called after init() with the record name and scan type.
98 fn set_record_info(&mut self, _name: &str, _scan: ScanType) {}
99
100 /// Return a receiver for I/O Intr scan notifications.
101 /// Only called for records with SCAN=I/O Intr.
102 fn io_intr_receiver(&mut self) -> Option<crate::runtime::sync::mpsc::Receiver<()>> {
103 None
104 }
105
106 /// Begin an asynchronous write (submit only, no blocking).
107 /// Returns `Some(handle)` if the write was submitted to a worker queue —
108 /// the caller should wait on the handle outside any record lock.
109 /// Returns `None` to fall back to synchronous [`write()`](DeviceSupport::write).
110 fn write_begin(
111 &mut self,
112 _record: &mut dyn Record,
113 ) -> CaResult<Option<Box<dyn WriteCompletion>>> {
114 Ok(None)
115 }
116
117 /// Handle a named command from the record's process() via
118 /// `ProcessAction::DeviceCommand`. This allows records to request
119 /// driver operations (e.g., scaler reset/arm/write_preset) without
120 /// holding a direct driver reference.
121 ///
122 /// Default: ignore.
123 fn handle_command(
124 &mut self,
125 _record: &mut dyn Record,
126 _command: &str,
127 _args: &[crate::types::EpicsValue],
128 ) -> CaResult<()> {
129 Ok(())
130 }
131}