use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use asyn_rs::error::AsynResult;
use asyn_rs::param::ParamType;
use asyn_rs::port::{PortDriverBase, PortFlags};
use asyn_rs::user::AsynUser;
use crate::snl::xiahsc::{
blades_from_height_center, blades_from_width_center, h_center_from_blades, height_from_blades,
v_center_from_blades, width_from_blades,
};
#[derive(Debug, Clone, Copy)]
pub struct HscParams {
pub h_gap: usize,
pub h_center: usize,
pub v_gap: usize,
pub v_center: usize,
pub top: usize,
pub bottom: usize,
pub left: usize,
pub right: usize,
pub top_rbv: usize,
pub bottom_rbv: usize,
pub left_rbv: usize,
pub right_rbv: usize,
pub h_gap_rbv: usize,
pub h_center_rbv: usize,
pub v_gap_rbv: usize,
pub v_center_rbv: usize,
pub busy: usize,
pub power_level: usize,
}
impl HscParams {
fn create(base: &mut PortDriverBase) -> AsynResult<Self> {
Ok(Self {
h_gap: base.create_param("H_GAP", ParamType::Float64)?,
h_center: base.create_param("H_CENTER", ParamType::Float64)?,
v_gap: base.create_param("V_GAP", ParamType::Float64)?,
v_center: base.create_param("V_CENTER", ParamType::Float64)?,
top: base.create_param("TOP", ParamType::Float64)?,
bottom: base.create_param("BOTTOM", ParamType::Float64)?,
left: base.create_param("LEFT", ParamType::Float64)?,
right: base.create_param("RIGHT", ParamType::Float64)?,
top_rbv: base.create_param("TOP_RBV", ParamType::Float64)?,
bottom_rbv: base.create_param("BOTTOM_RBV", ParamType::Float64)?,
left_rbv: base.create_param("LEFT_RBV", ParamType::Float64)?,
right_rbv: base.create_param("RIGHT_RBV", ParamType::Float64)?,
h_gap_rbv: base.create_param("H_GAP_RBV", ParamType::Float64)?,
h_center_rbv: base.create_param("H_CENTER_RBV", ParamType::Float64)?,
v_gap_rbv: base.create_param("V_GAP_RBV", ParamType::Float64)?,
v_center_rbv: base.create_param("V_CENTER_RBV", ParamType::Float64)?,
busy: base.create_param("BUSY", ParamType::Int32)?,
power_level: base.create_param("POWER_LEVEL", ParamType::Int32)?,
})
}
}
#[derive(Debug, Clone)]
struct SimBlade {
position: f64,
target: f64,
velocity: f64,
moving: bool,
move_start: Option<Instant>,
start_position: f64,
}
impl SimBlade {
fn new() -> Self {
Self {
position: 0.0,
target: 0.0,
velocity: 5.0,
moving: false,
move_start: None,
start_position: 0.0,
}
}
fn move_to(&mut self, target: f64) {
self.target = target;
self.start_position = self.position;
self.moving = true;
self.move_start = Some(Instant::now());
}
fn update(&mut self) {
if !self.moving {
return;
}
let elapsed = self
.move_start
.map(|s| s.elapsed().as_secs_f64())
.unwrap_or(0.0);
let distance = (self.target - self.start_position).abs();
let travel_time = if self.velocity > 0.0 {
distance / self.velocity
} else {
0.0
};
if elapsed >= travel_time {
self.position = self.target;
self.moving = false;
} else {
let fraction = elapsed / travel_time;
self.position = self.start_position + (self.target - self.start_position) * fraction;
}
}
}
pub struct SimHsc {
top: SimBlade,
bottom: SimBlade,
left: SimBlade,
right: SimBlade,
power_level: i32,
}
impl SimHsc {
pub fn new() -> Self {
Self {
top: SimBlade::new(),
bottom: SimBlade::new(),
left: SimBlade::new(),
right: SimBlade::new(),
power_level: 0,
}
}
pub fn move_blades(&mut self, top: f64, bottom: f64, left: f64, right: f64) {
self.top.move_to(top);
self.bottom.move_to(bottom);
self.left.move_to(left);
self.right.move_to(right);
}
pub fn move_h_gap_center(&mut self, gap: f64, center: f64) {
let (left, right) = blades_from_width_center(gap, center);
self.left.move_to(left);
self.right.move_to(right);
}
pub fn move_v_gap_center(&mut self, gap: f64, center: f64) {
let (top, bottom) = blades_from_height_center(gap, center);
self.top.move_to(top);
self.bottom.move_to(bottom);
}
pub fn update(&mut self) {
self.top.update();
self.bottom.update();
self.left.update();
self.right.update();
}
pub fn is_moving(&self) -> bool {
self.top.moving || self.bottom.moving || self.left.moving || self.right.moving
}
pub fn positions(&self) -> (f64, f64, f64, f64) {
(
self.top.position,
self.bottom.position,
self.left.position,
self.right.position,
)
}
pub fn set_power_level(&mut self, level: i32) {
self.power_level = level.clamp(0, 2);
}
pub fn power_level(&self) -> i32 {
self.power_level
}
}
impl Default for SimHsc {
fn default() -> Self {
Self::new()
}
}
pub struct HscDriver {
base: PortDriverBase,
params: HscParams,
sim: Arc<Mutex<SimHsc>>,
}
impl HscDriver {
pub fn new(port_name: &str, sim: Arc<Mutex<SimHsc>>) -> AsynResult<Self> {
let flags = PortFlags {
multi_device: false,
can_block: false,
destructible: true,
};
let mut base = PortDriverBase::new(port_name, 1, flags);
let params = HscParams::create(&mut base)?;
base.set_float64_param(params.h_gap, 0, 0.0)?;
base.set_float64_param(params.h_center, 0, 0.0)?;
base.set_float64_param(params.v_gap, 0, 0.0)?;
base.set_float64_param(params.v_center, 0, 0.0)?;
base.set_float64_param(params.top, 0, 0.0)?;
base.set_float64_param(params.bottom, 0, 0.0)?;
base.set_float64_param(params.left, 0, 0.0)?;
base.set_float64_param(params.right, 0, 0.0)?;
base.set_float64_param(params.top_rbv, 0, 0.0)?;
base.set_float64_param(params.bottom_rbv, 0, 0.0)?;
base.set_float64_param(params.left_rbv, 0, 0.0)?;
base.set_float64_param(params.right_rbv, 0, 0.0)?;
base.set_float64_param(params.h_gap_rbv, 0, 0.0)?;
base.set_float64_param(params.h_center_rbv, 0, 0.0)?;
base.set_float64_param(params.v_gap_rbv, 0, 0.0)?;
base.set_float64_param(params.v_center_rbv, 0, 0.0)?;
base.set_int32_param(params.busy, 0, 0)?;
base.set_int32_param(params.power_level, 0, 0)?;
Ok(Self { base, params, sim })
}
pub fn params(&self) -> &HscParams {
&self.params
}
pub fn poll(&mut self) -> AsynResult<()> {
let (top, bottom, left, right, moving, power_level) = {
let mut sim = self.sim.lock().unwrap();
sim.update();
let (t, b, l, r) = sim.positions();
(t, b, l, r, sim.is_moving(), sim.power_level())
};
self.base.set_float64_param(self.params.top_rbv, 0, top)?;
self.base
.set_float64_param(self.params.bottom_rbv, 0, bottom)?;
self.base.set_float64_param(self.params.left_rbv, 0, left)?;
self.base
.set_float64_param(self.params.right_rbv, 0, right)?;
let h_gap = width_from_blades(left, right);
let h_center = h_center_from_blades(left, right);
let v_gap = height_from_blades(top, bottom);
let v_center = v_center_from_blades(top, bottom);
self.base
.set_float64_param(self.params.h_gap_rbv, 0, h_gap)?;
self.base
.set_float64_param(self.params.h_center_rbv, 0, h_center)?;
self.base
.set_float64_param(self.params.v_gap_rbv, 0, v_gap)?;
self.base
.set_float64_param(self.params.v_center_rbv, 0, v_center)?;
self.base
.set_int32_param(self.params.busy, 0, if moving { 1 } else { 0 })?;
self.base
.set_int32_param(self.params.power_level, 0, power_level)?;
self.base.call_param_callbacks(0)?;
Ok(())
}
fn handle_float64_write(&mut self, reason: usize, value: f64) -> AsynResult<()> {
let p = &self.params;
if reason == p.h_gap {
let center = self.base.get_float64_param(p.h_center, 0).unwrap_or(0.0);
let mut sim = self.sim.lock().unwrap();
sim.move_h_gap_center(value, center);
} else if reason == p.h_center {
let gap = self.base.get_float64_param(p.h_gap, 0).unwrap_or(0.0);
let mut sim = self.sim.lock().unwrap();
sim.move_h_gap_center(gap, value);
} else if reason == p.v_gap {
let center = self.base.get_float64_param(p.v_center, 0).unwrap_or(0.0);
let mut sim = self.sim.lock().unwrap();
sim.move_v_gap_center(value, center);
} else if reason == p.v_center {
let gap = self.base.get_float64_param(p.v_gap, 0).unwrap_or(0.0);
let mut sim = self.sim.lock().unwrap();
sim.move_v_gap_center(gap, value);
} else if reason == p.top || reason == p.bottom || reason == p.left || reason == p.right {
let top_val = if reason == p.top {
value
} else {
self.base.get_float64_param(p.top, 0).unwrap_or(0.0)
};
let bottom_val = if reason == p.bottom {
value
} else {
self.base.get_float64_param(p.bottom, 0).unwrap_or(0.0)
};
let left_val = if reason == p.left {
value
} else {
self.base.get_float64_param(p.left, 0).unwrap_or(0.0)
};
let right_val = if reason == p.right {
value
} else {
self.base.get_float64_param(p.right, 0).unwrap_or(0.0)
};
let mut sim = self.sim.lock().unwrap();
sim.move_blades(top_val, bottom_val, left_val, right_val);
}
Ok(())
}
}
impl asyn_rs::port::PortDriver for HscDriver {
fn base(&self) -> &PortDriverBase {
&self.base
}
fn base_mut(&mut self) -> &mut PortDriverBase {
&mut self.base
}
fn write_float64(&mut self, user: &mut AsynUser, value: f64) -> AsynResult<()> {
self.base().check_ready()?;
let reason = user.reason;
self.base_mut()
.params
.set_float64(reason, user.addr, value)?;
self.handle_float64_write(reason, value)?;
self.base_mut().call_param_callbacks(user.addr)
}
fn write_int32(&mut self, user: &mut AsynUser, value: i32) -> AsynResult<()> {
self.base().check_ready()?;
let reason = user.reason;
self.base_mut().params.set_int32(reason, user.addr, value)?;
if reason == self.params.power_level {
let mut sim = self.sim.lock().unwrap();
sim.set_power_level(value);
}
self.base_mut().call_param_callbacks(user.addr)
}
}
#[derive(Debug)]
pub enum HscPollCommand {
StartPolling,
Shutdown,
}
pub struct HscPollLoop {
cmd_rx: tokio::sync::mpsc::Receiver<HscPollCommand>,
driver: Arc<Mutex<HscDriver>>,
poll_interval: Duration,
}
impl HscPollLoop {
pub fn new(
cmd_rx: tokio::sync::mpsc::Receiver<HscPollCommand>,
driver: Arc<Mutex<HscDriver>>,
poll_interval: Duration,
) -> Self {
Self {
cmd_rx,
driver,
poll_interval,
}
}
pub async fn run(mut self) {
match self.cmd_rx.recv().await {
Some(HscPollCommand::StartPolling) => {}
Some(HscPollCommand::Shutdown) | None => return,
}
loop {
tokio::select! {
cmd = self.cmd_rx.recv() => {
match cmd {
Some(HscPollCommand::StartPolling) => {}
Some(HscPollCommand::Shutdown) | None => return,
}
}
_ = tokio::time::sleep(self.poll_interval) => {
if let Ok(mut driver) = self.driver.lock() {
let _ = driver.poll();
}
}
}
}
}
}
pub struct HscHolder {
drivers: Mutex<HashMap<String, Arc<Mutex<HscDriver>>>>,
poll_senders: Mutex<Vec<tokio::sync::mpsc::Sender<HscPollCommand>>>,
}
impl HscHolder {
pub fn new() -> Arc<Self> {
Arc::new(Self {
drivers: 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(HscPollCommand::StartPolling);
}
}
pub fn sim_hsc_create_command(
self: &Arc<Self>,
) -> epics_base_rs::server::iocsh::registry::CommandDef {
use epics_base_rs::server::iocsh::registry::*;
let holder = self.clone();
CommandDef::new(
"simHscCreate",
vec![
ArgDesc {
name: "port",
arg_type: ArgType::String,
optional: false,
},
ArgDesc {
name: "pollMs",
arg_type: ArgType::Int,
optional: true,
},
],
"simHscCreate(port, [pollMs]) - Create a simulated HSC-1 slit controller",
move |args: &[ArgValue], ctx: &CommandContext| {
let port = match &args[0] {
ArgValue::String(s) => s.clone(),
_ => return Err("port must be a string".into()),
};
let poll_ms = match &args[1] {
ArgValue::Int(v) => *v as u64,
ArgValue::Missing => 100,
_ => return Err("pollMs must be an integer".into()),
};
let sim = Arc::new(Mutex::new(SimHsc::new()));
let driver = match HscDriver::new(&port, sim) {
Ok(d) => Arc::new(Mutex::new(d)),
Err(e) => return Err(format!("failed to create HscDriver: {e}")),
};
let (cmd_tx, cmd_rx) = tokio::sync::mpsc::channel(16);
let poll_loop =
HscPollLoop::new(cmd_rx, driver.clone(), Duration::from_millis(poll_ms));
ctx.runtime_handle().spawn(poll_loop.run());
holder.poll_senders.lock().unwrap().push(cmd_tx);
holder.drivers.lock().unwrap().insert(port.clone(), driver);
println!("simHscCreate: port={port} poll={poll_ms}ms");
Ok(CommandOutcome::Continue)
},
)
}
pub fn get_driver(&self, port: &str) -> Option<Arc<Mutex<HscDriver>>> {
self.drivers.lock().unwrap().get(port).cloned()
}
}
impl Default for HscHolder {
fn default() -> Self {
Self {
drivers: Mutex::new(HashMap::new()),
poll_senders: Mutex::new(Vec::new()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use asyn_rs::port::PortDriver;
#[test]
fn test_sim_hsc_initial_state() {
let sim = SimHsc::new();
let (t, b, l, r) = sim.positions();
assert_eq!(t, 0.0);
assert_eq!(b, 0.0);
assert_eq!(l, 0.0);
assert_eq!(r, 0.0);
assert!(!sim.is_moving());
}
#[test]
fn test_sim_hsc_move_completes() {
let mut sim = SimHsc::new();
sim.top.velocity = 100000.0;
sim.bottom.velocity = 100000.0;
sim.left.velocity = 100000.0;
sim.right.velocity = 100000.0;
sim.move_blades(1.0, 2.0, 3.0, 4.0);
std::thread::sleep(Duration::from_millis(10));
sim.update();
let (t, b, l, r) = sim.positions();
assert_eq!(t, 1.0);
assert_eq!(b, 2.0);
assert_eq!(l, 3.0);
assert_eq!(r, 4.0);
assert!(!sim.is_moving());
}
#[test]
fn test_sim_hsc_gap_center_horizontal() {
let mut sim = SimHsc::new();
sim.left.velocity = 100000.0;
sim.right.velocity = 100000.0;
sim.move_h_gap_center(2.0, 0.5);
std::thread::sleep(Duration::from_millis(10));
sim.update();
let (_, _, l, r) = sim.positions();
assert!((l - 0.5).abs() < 1e-9);
assert!((r - 1.5).abs() < 1e-9);
}
#[test]
fn test_sim_hsc_gap_center_vertical() {
let mut sim = SimHsc::new();
sim.top.velocity = 100000.0;
sim.bottom.velocity = 100000.0;
sim.move_v_gap_center(4.0, 1.0);
std::thread::sleep(Duration::from_millis(10));
sim.update();
let (t, b, _, _) = sim.positions();
assert!((t - 3.0).abs() < 1e-9);
assert!((b - 1.0).abs() < 1e-9);
}
#[test]
fn test_sim_hsc_power_level() {
let mut sim = SimHsc::new();
assert_eq!(sim.power_level(), 0);
sim.set_power_level(2);
assert_eq!(sim.power_level(), 2);
sim.set_power_level(5); assert_eq!(sim.power_level(), 2);
sim.set_power_level(-1); assert_eq!(sim.power_level(), 0);
}
#[test]
fn test_hsc_driver_poll_updates_readbacks() {
let sim = Arc::new(Mutex::new(SimHsc::new()));
{
let mut s = sim.lock().unwrap();
s.top.position = 3.0;
s.bottom.position = 1.0;
s.left.position = 0.5;
s.right.position = 1.5;
}
let mut driver = HscDriver::new("test_hsc", sim).unwrap();
driver.poll().unwrap();
let p = driver.params();
assert_eq!(driver.base.get_float64_param(p.top_rbv, 0).unwrap(), 3.0);
assert_eq!(driver.base.get_float64_param(p.bottom_rbv, 0).unwrap(), 1.0);
assert_eq!(driver.base.get_float64_param(p.left_rbv, 0).unwrap(), 0.5);
assert_eq!(driver.base.get_float64_param(p.right_rbv, 0).unwrap(), 1.5);
assert!((driver.base.get_float64_param(p.h_gap_rbv, 0).unwrap() - 2.0).abs() < 1e-9);
assert!((driver.base.get_float64_param(p.h_center_rbv, 0).unwrap() - 0.5).abs() < 1e-9);
assert!((driver.base.get_float64_param(p.v_gap_rbv, 0).unwrap() - 4.0).abs() < 1e-9);
assert!((driver.base.get_float64_param(p.v_center_rbv, 0).unwrap() - 1.0).abs() < 1e-9);
}
#[test]
fn test_hsc_driver_write_h_gap() {
let sim = Arc::new(Mutex::new(SimHsc::new()));
let mut driver = HscDriver::new("test_hsc_gap", sim.clone()).unwrap();
let p = driver.params;
driver.base.set_float64_param(p.h_center, 0, 0.5).unwrap();
let mut user = AsynUser::new(p.h_gap);
driver.write_float64(&mut user, 2.0).unwrap();
{
let s = sim.lock().unwrap();
assert!((s.left.target - 0.5).abs() < 1e-9);
assert!((s.right.target - 1.5).abs() < 1e-9);
}
}
#[test]
fn test_hsc_driver_write_individual_blades() {
let sim = Arc::new(Mutex::new(SimHsc::new()));
let mut driver = HscDriver::new("test_hsc_blades", sim.clone()).unwrap();
let p = driver.params;
driver.base.set_float64_param(p.top, 0, 1.0).unwrap();
driver.base.set_float64_param(p.bottom, 0, 2.0).unwrap();
driver.base.set_float64_param(p.left, 0, 3.0).unwrap();
let mut user = AsynUser::new(p.right);
driver.write_float64(&mut user, 4.0).unwrap();
{
let s = sim.lock().unwrap();
assert!((s.top.target - 1.0).abs() < 1e-9);
assert!((s.bottom.target - 2.0).abs() < 1e-9);
assert!((s.left.target - 3.0).abs() < 1e-9);
assert!((s.right.target - 4.0).abs() < 1e-9);
}
}
#[test]
fn test_hsc_driver_write_power_level() {
let sim = Arc::new(Mutex::new(SimHsc::new()));
let mut driver = HscDriver::new("test_hsc_pwr", sim.clone()).unwrap();
let p = driver.params;
let mut user = AsynUser::new(p.power_level);
driver.write_int32(&mut user, 2).unwrap();
assert_eq!(sim.lock().unwrap().power_level(), 2);
assert_eq!(driver.base.get_int32_param(p.power_level, 0).unwrap(), 2);
}
#[test]
fn test_hsc_driver_busy_flag() {
let sim = Arc::new(Mutex::new(SimHsc::new()));
let mut driver = HscDriver::new("test_hsc_busy", sim.clone()).unwrap();
driver.poll().unwrap();
assert_eq!(
driver.base.get_int32_param(driver.params.busy, 0).unwrap(),
0
);
{
let mut s = sim.lock().unwrap();
s.left.velocity = 0.001; s.left.move_to(100.0);
}
driver.poll().unwrap();
assert_eq!(
driver.base.get_int32_param(driver.params.busy, 0).unwrap(),
1
);
}
#[test]
fn test_hsc_holder_creation() {
let holder = HscHolder::new();
assert!(holder.get_driver("nonexistent").is_none());
}
#[test]
fn test_sim_blade_partial_move() {
let mut blade = SimBlade::new();
blade.velocity = 1.0; blade.move_to(10.0);
blade.update();
assert!(blade.position < 10.0);
assert!(blade.moving);
}
}