use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use epics_base_rs::server::iocsh::registry::*;
use crate::builder::MotorBuilder;
use crate::device_support::MotorDeviceSupport;
use crate::sim_motor::SimMotor;
pub struct SimMotorHolder {
motors: Mutex<HashMap<String, Option<MotorDeviceSupport>>>,
poll_senders: Mutex<Vec<tokio::sync::mpsc::Sender<crate::poll_loop::PollCommand>>>,
}
impl SimMotorHolder {
pub fn new() -> Arc<Self> {
Arc::new(Self {
motors: Mutex::new(HashMap::new()),
poll_senders: Mutex::new(Vec::new()),
})
}
pub fn start_all_polling(&self) {
for tx in self.poll_senders.lock().unwrap().iter() {
let _ = tx.try_send(crate::poll_loop::PollCommand::StartPolling);
}
}
pub fn sim_motor_create_command(self: &Arc<Self>) -> CommandDef {
let holder = self.clone();
CommandDef::new(
"simMotorCreate",
vec![
ArgDesc {
name: "port",
arg_type: ArgType::String,
optional: false,
},
ArgDesc {
name: "lowLimit",
arg_type: ArgType::Double,
optional: false,
},
ArgDesc {
name: "highLimit",
arg_type: ArgType::Double,
optional: false,
},
ArgDesc {
name: "pollMs",
arg_type: ArgType::Int,
optional: true,
},
],
"simMotorCreate(port, lowLimit, highLimit, [pollMs]) - Create a simulated motor",
move |args: &[ArgValue], ctx: &CommandContext| {
let port = match &args[0] {
ArgValue::String(s) => s.clone(),
_ => return Err("port must be a string".into()),
};
let low_limit = match &args[1] {
ArgValue::Double(v) => *v,
_ => return Err("lowLimit must be a number".into()),
};
let high_limit = match &args[2] {
ArgValue::Double(v) => *v,
_ => return Err("highLimit must be a number".into()),
};
let poll_ms = match &args[3] {
ArgValue::Int(v) => *v as u64,
ArgValue::Missing => 100,
_ => return Err("pollMs must be an integer".into()),
};
let dtyp_key = format!("simMotor_{port}");
let motor: Arc<Mutex<dyn asyn_rs::interfaces::motor::AsynMotor>> = Arc::new(
Mutex::new(SimMotor::new().with_limits(low_limit, high_limit)),
);
let setup = MotorBuilder::new(motor)
.poll_interval(Duration::from_millis(poll_ms))
.build();
let crate::builder::MotorSetup {
record: _,
device_support,
poll_loop,
poll_cmd_tx,
} = setup;
let device_support = device_support.with_dtyp_name(dtyp_key.clone());
ctx.runtime_handle().spawn(poll_loop.run());
holder.poll_senders.lock().unwrap().push(poll_cmd_tx);
holder
.motors
.lock()
.unwrap()
.insert(dtyp_key.clone(), Some(device_support));
println!(
"simMotorCreate: port={port} limits=[{low_limit}, {high_limit}] poll={poll_ms}ms (DTYP={dtyp_key})"
);
Ok(CommandOutcome::Continue)
},
)
}
pub fn device_support_factory(
self: &Arc<Self>,
) -> impl Fn(
&epics_ca_rs::server::ioc_app::DeviceSupportContext,
) -> Option<Box<dyn epics_base_rs::server::device_support::DeviceSupport>>
+ Send
+ Sync
+ 'static {
let holder = self.clone();
move |ctx: &epics_ca_rs::server::ioc_app::DeviceSupportContext| {
let mut motors = holder.motors.lock().unwrap();
if let Some(slot) = motors.get_mut(ctx.dtyp) {
if let Some(ds) = slot.take() {
return Some(Box::new(ds)
as Box<dyn epics_base_rs::server::device_support::DeviceSupport>);
}
}
None
}
}
}