use std::sync::{Arc, Mutex};
use std::time::Duration;
use asyn_rs::interfaces::motor::AsynMotor;
use asyn_rs::user::AsynUser;
use epics_base_rs::error::CaResult;
use epics_base_rs::server::device_support::{DeviceReadOutcome, DeviceSupport};
use epics_base_rs::server::record::{Record, ScanType};
use epics_base_rs::types::EpicsValue;
use tokio::sync::mpsc;
use crate::device_state::*;
use crate::flags::*;
use crate::poll_loop::PollCommand;
pub struct MotorDeviceSupport {
motor: Arc<Mutex<dyn AsynMotor>>,
_addr: i32,
_timeout: Duration,
poll_cmd_tx: mpsc::Sender<PollCommand>,
io_intr_tx: mpsc::Sender<()>,
io_intr_rx: Option<mpsc::Receiver<()>>,
device_state: SharedDeviceState,
initialized: bool,
dtyp_name: String,
polling_active: bool,
}
impl MotorDeviceSupport {
pub fn new(
motor: Arc<Mutex<dyn AsynMotor>>,
addr: i32,
timeout: Duration,
poll_cmd_tx: mpsc::Sender<PollCommand>,
device_state: SharedDeviceState,
) -> Self {
let (io_intr_tx, io_intr_rx) = mpsc::channel(16);
Self {
motor,
_addr: addr,
_timeout: timeout,
poll_cmd_tx,
io_intr_tx,
io_intr_rx: Some(io_intr_rx),
device_state,
initialized: false,
dtyp_name: "asynMotor".to_string(),
polling_active: false,
}
}
pub fn with_dtyp_name(mut self, name: String) -> Self {
self.dtyp_name = name;
self
}
pub fn io_intr_sender(&self) -> mpsc::Sender<()> {
self.io_intr_tx.clone()
}
fn make_user(&self) -> AsynUser {
AsynUser::new(0)
}
fn execute_actions(&mut self, actions: &DeviceActions) {
let user = self.make_user();
let mut motor = match self.motor.lock() {
Ok(m) => m,
Err(e) => {
tracing::error!("motor lock poisoned: {e}");
return;
}
};
for cmd in &actions.commands {
let result = match cmd {
MotorCommand::MoveAbsolute {
position,
velocity,
acceleration,
} => {
tracing::info!("motor command: MoveAbsolute pos={position}, vel={velocity}");
motor.move_absolute(&user, *position, *velocity, *acceleration)
}
MotorCommand::MoveRelative {
distance,
velocity,
acceleration,
} => {
tracing::info!("motor command: MoveRelative dist={distance}, vel={velocity}");
motor.move_relative(&user, *distance, *velocity, *acceleration)
}
MotorCommand::MoveVelocity {
direction,
velocity,
acceleration,
} => {
let signed_vel = if *direction { *velocity } else { -*velocity };
tracing::info!("motor command: MoveVelocity dir={direction}, vel={velocity}");
motor.move_velocity(&user, signed_vel, *acceleration)
}
MotorCommand::Home {
forward,
velocity,
acceleration: _,
} => {
tracing::info!("motor command: Home forward={forward}");
motor.home(&user, *velocity, *forward)
}
MotorCommand::Stop { acceleration } => {
tracing::info!("motor command: Stop");
motor.stop(&user, *acceleration)
}
MotorCommand::SetPosition { position } => {
tracing::info!("motor command: SetPosition pos={position}");
motor.set_position(&user, *position)
}
MotorCommand::SetClosedLoop { enable } => {
tracing::info!("motor command: SetClosedLoop enable={enable}");
motor.set_closed_loop(&user, *enable)
}
MotorCommand::DeferMoves { defer } => {
tracing::info!("motor command: DeferMoves defer={defer}");
motor.set_deferred_moves(&user, *defer)
}
MotorCommand::ProfileInitialize { max_points } => {
tracing::info!("motor command: ProfileInitialize max_points={max_points}");
motor.initialize_profile(&user, *max_points)
}
MotorCommand::ProfileBuild => {
tracing::info!("motor command: ProfileBuild");
motor.build_profile(&user)
}
MotorCommand::ProfileExecute => {
tracing::info!("motor command: ProfileExecute");
motor.execute_profile(&user)
}
MotorCommand::ProfileAbort => {
tracing::info!("motor command: ProfileAbort");
motor.abort_profile(&user)
}
MotorCommand::ProfileReadback => {
tracing::info!("motor command: ProfileReadback");
motor.readback_profile(&user).map(|_| ())
}
MotorCommand::Poll => Ok(()),
};
if let Err(e) = result {
tracing::error!("motor command error: {e}");
}
}
drop(motor);
match actions.poll {
PollDirective::Start => {
if !self.polling_active {
let _ = self.poll_cmd_tx.try_send(PollCommand::StartPolling);
self.polling_active = true;
}
}
PollDirective::Stop => {
let _ = self.poll_cmd_tx.try_send(PollCommand::StopPolling);
self.polling_active = false;
}
PollDirective::None => {}
}
if let Some(ref delay) = actions.schedule_delay {
let _ = self
.poll_cmd_tx
.try_send(PollCommand::ScheduleDelay(delay.id, delay.duration));
self.polling_active = false;
}
}
}
impl DeviceSupport for MotorDeviceSupport {
fn init(&mut self, record: &mut dyn Record) -> CaResult<()> {
let motor_rec = record
.as_any_mut()
.and_then(|a| a.downcast_mut::<crate::record::MotorRecord>());
if let Some(motor_rec) = motor_rec {
motor_rec.set_device_state(self.device_state.clone());
}
let user = self.make_user();
let dval = record
.get_field("DVAL")
.and_then(|v| match v {
EpicsValue::Double(d) => Some(d),
_ => None,
})
.unwrap_or(0.0);
if dval != 0.0 {
let mut motor = self.motor.lock().map_err(|e| {
epics_base_rs::error::CaError::InvalidValue(format!("motor lock: {e}"))
})?;
let _ = motor.set_position(&user, dval);
}
let status = {
let mut motor = self.motor.lock().map_err(|e| {
epics_base_rs::error::CaError::InvalidValue(format!("motor lock: {e}"))
})?;
motor.poll(&user).map_err(|e| {
epics_base_rs::error::CaError::InvalidValue(format!("motor poll: {e}"))
})?
};
let mut ds = self.device_state.lock().map_err(|e| {
epics_base_rs::error::CaError::InvalidValue(format!("device state lock: {e}"))
})?;
ds.latest_status = Some(StampedStatus {
seq: 1,
status: status.clone(),
});
drop(ds);
if let Some(motor_rec) = record
.as_any_mut()
.and_then(|a| a.downcast_mut::<crate::record::MotorRecord>())
{
motor_rec.process_motor_info(&status);
motor_rec.clear_last_write();
}
self.initialized = true;
Ok(())
}
fn read(&mut self, _record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
Ok(DeviceReadOutcome::ok())
}
fn write(&mut self, _record: &mut dyn Record) -> CaResult<()> {
let actions = {
let mut ds = self.device_state.lock().map_err(|e| {
epics_base_rs::error::CaError::InvalidValue(format!("device state lock: {e}"))
})?;
ds.pending_actions.take()
};
let Some(actions) = actions else {
return Ok(());
};
self.execute_actions(&actions);
Ok(())
}
fn dtyp(&self) -> &str {
&self.dtyp_name
}
fn set_record_info(&mut self, _name: &str, _scan: ScanType) {}
fn io_intr_receiver(&mut self) -> Option<mpsc::Receiver<()>> {
self.io_intr_rx.take()
}
}