mod command_planner;
mod field_access;
mod state_machine;
mod status_update;
use epics_base_rs::error::CaResult;
use epics_base_rs::server::record::{FieldDesc, ProcessOutcome, Record, RecordProcessResult};
use epics_base_rs::types::EpicsValue;
use crate::coordinate;
use crate::device_state::*;
use crate::fields::*;
use crate::flags::*;
#[derive(Debug, Clone)]
pub struct MotorRecord {
pub pos: PositionFields,
pub conv: ConversionFields,
pub vel: VelocityFields,
pub retry: RetryFields,
pub limits: LimitFields,
pub ctrl: ControlFields,
pub stat: StatusFields,
pub pid: PidFields,
pub disp: DisplayFields,
pub timing: TimingFields,
pub internal: InternalFields,
pending_event: Option<MotorEvent>,
last_write: Option<CommandSource>,
suppress_flnk: bool,
device_state: Option<SharedDeviceState>,
last_seen_seq: u64,
initialized: bool,
next_delay_id: u64,
}
impl Default for MotorRecord {
fn default() -> Self {
Self {
pos: PositionFields::default(),
conv: ConversionFields::default(),
vel: VelocityFields::default(),
retry: RetryFields::default(),
limits: LimitFields::default(),
ctrl: ControlFields::default(),
stat: StatusFields::default(),
pid: PidFields::default(),
disp: DisplayFields::default(),
timing: TimingFields::default(),
internal: InternalFields::default(),
pending_event: None,
last_write: None,
suppress_flnk: false,
device_state: None,
last_seen_seq: 0,
initialized: false,
next_delay_id: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MotionDirection {
Positive,
Negative,
}
impl MotorRecord {
pub fn new() -> Self {
Self::default()
}
pub fn with_device_state(mut self, state: SharedDeviceState) -> Self {
self.device_state = Some(state);
self
}
pub fn set_device_state(&mut self, state: SharedDeviceState) {
self.device_state = Some(state);
}
pub fn set_event(&mut self, event: MotorEvent) {
self.pending_event = Some(event);
}
pub fn clear_last_write(&mut self) {
self.last_write = None;
}
}
impl Record for MotorRecord {
fn record_type(&self) -> &'static str {
"motor"
}
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
Some(self)
}
fn can_device_write(&self) -> bool {
true
}
fn is_put_complete(&self) -> bool {
self.stat.dmov
}
fn process(&mut self) -> CaResult<ProcessOutcome> {
if self.device_state.is_some() {
if let Some(event) = self.determine_event() {
self.pending_event = Some(event);
}
}
let effects = self.do_process();
let move_started = !self.stat.dmov;
if let Some(state) = self.device_state.clone() {
self.suppress_flnk = effects.suppress_forward_link;
let actions = self.effects_to_actions(&effects);
match state.lock() {
Ok(mut ds) => {
ds.pending_actions = Some(actions);
}
Err(e) => {
tracing::error!("device state lock poisoned in process: {e}");
}
}
}
if move_started && !self.internal.dmov_notified {
self.internal.dmov_notified = true;
use epics_base_rs::types::EpicsValue;
let fields = vec![
("DMOV".to_string(), EpicsValue::Short(0)),
("MOVN".to_string(), EpicsValue::Short(1)),
("VAL".to_string(), EpicsValue::Double(self.pos.val)),
("DVAL".to_string(), EpicsValue::Double(self.pos.dval)),
("RVAL".to_string(), EpicsValue::Long(self.pos.rval)),
("RBV".to_string(), EpicsValue::Double(self.pos.rbv)),
("DRBV".to_string(), EpicsValue::Double(self.pos.drbv)),
];
Ok(ProcessOutcome {
result: RecordProcessResult::AsyncPendingNotify(fields),
actions: Vec::new(),
device_did_compute: false,
})
} else {
if !move_started {
self.internal.dmov_notified = false;
}
Ok(ProcessOutcome::complete())
}
}
fn should_fire_forward_link(&self) -> bool {
!self.suppress_flnk
}
fn get_field(&self, name: &str) -> Option<EpicsValue> {
field_access::motor_get_field(self, name)
}
fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
field_access::motor_put_field(self, name, value)
}
fn field_list(&self) -> &'static [FieldDesc] {
field_access::FIELDS
}
fn primary_field(&self) -> &'static str {
"VAL"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_mode_updates_offset() {
let mut rec = MotorRecord::new();
rec.conv.mres = 0.01;
rec.pos.dval = 5.0;
rec.conv.set = true;
rec.put_field("VAL", EpicsValue::Double(100.0)).unwrap();
assert_eq!(rec.pos.dval, 5.0);
assert_eq!(rec.pos.off, 95.0); assert_eq!(rec.last_write, Some(CommandSource::Set));
}
#[test]
fn test_should_fire_forward_link() {
let mut rec = MotorRecord::new();
assert!(rec.should_fire_forward_link());
rec.suppress_flnk = true;
assert!(!rec.should_fire_forward_link());
}
}