use std::collections::HashMap;
use epics_base_rs::server::database::PvDatabase;
pub fn parse_macros(input: &str) -> HashMap<String, String> {
let mut map = HashMap::new();
for part in input.split(',') {
let part = part.trim();
if let Some((k, v)) = part.split_once('=') {
map.insert(k.trim().to_string(), v.trim().to_string());
}
}
map
}
fn require_macro(
macros: &HashMap<String, String>,
key: &str,
program: &str,
) -> Result<String, String> {
macros
.get(key)
.cloned()
.ok_or_else(|| format!("{program}: required macro '{key}' not specified"))
}
fn macro_or(macros: &HashMap<String, String>, key: &str, default: &str) -> String {
macros
.get(key)
.cloned()
.unwrap_or_else(|| default.to_string())
}
pub fn seq_start(
program: &str,
macro_str: &str,
handle: &tokio::runtime::Handle,
db: &PvDatabase,
) -> Result<(), String> {
let macros = parse_macros(macro_str);
match program {
"kohzuCtl" => {
let config = crate::snl::kohzu_ctl::KohzuConfig::new(
&require_macro(¯os, "P", program)?,
&require_macro(¯os, "M_THETA", program)?,
&require_macro(¯os, "M_Y", program)?,
&require_macro(¯os, "M_Z", program)?,
macro_or(¯os, "GEOM", "0").parse::<i32>().unwrap_or(0),
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::kohzu_ctl::run(config, db).await {
eprintln!("kohzuCtl error: {e}");
}
});
}
"kohzuCtl_soft" => {
let config = crate::snl::kohzu_ctl_soft::KohzuSoftConfig::new(
&require_macro(¯os, "P", program)?,
¯o_or(¯os, "MONO", ""),
&require_macro(¯os, "M_THETA", program)?,
&require_macro(¯os, "M_Y", program)?,
&require_macro(¯os, "M_Z", program)?,
macro_or(¯os, "GEOM", "0").parse::<i32>().unwrap_or(0),
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::kohzu_ctl_soft::run(config, db).await {
eprintln!("kohzuCtl_soft error: {e}");
}
});
}
"hrCtl" => {
let config = crate::snl::hr_ctl::HrConfig::new(
&require_macro(¯os, "P", program)?,
¯o_or(¯os, "N", "1"),
&require_macro(¯os, "M_PHI1", program)?,
&require_macro(¯os, "M_PHI2", program)?,
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::hr_ctl::run(config, db).await {
eprintln!("hrCtl error: {e}");
}
});
}
"ml_monoCtl" => {
let config = crate::snl::ml_mono_ctl::MlMonoConfig::new(
&require_macro(¯os, "P", program)?,
&require_macro(¯os, "M_THETA", program)?,
¯o_or(¯os, "M_THETA2", ""),
¯o_or(¯os, "M_Y", ""),
¯o_or(¯os, "M_Z", ""),
macro_or(¯os, "Y_OFFSET", "35.0")
.parse::<f64>()
.unwrap_or(35.0),
macro_or(¯os, "GEOM", "0").parse::<i32>().unwrap_or(0),
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::ml_mono_ctl::run(config, db).await {
eprintln!("ml_monoCtl error: {e}");
}
});
}
"orient" => {
let config = crate::snl::orient::OrientConfig::new(
&require_macro(¯os, "P", program)?,
&require_macro(¯os, "PM", program)?,
&require_macro(¯os, "mTTH", program)?,
&require_macro(¯os, "mTH", program)?,
&require_macro(¯os, "mCHI", program)?,
&require_macro(¯os, "mPHI", program)?,
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::orient::run(config, db).await {
eprintln!("orient error: {e}");
}
});
}
"filterDrive" => {
let config = crate::snl::filter_drive::FilterDriveConfig::new(
&require_macro(¯os, "P", program)?,
&require_macro(¯os, "R", program)?,
macro_or(¯os, "N", "8").parse::<usize>().unwrap_or(8),
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::filter_drive::run(config, db).await {
eprintln!("filterDrive error: {e}");
}
});
}
"pf4" => {
let config = crate::snl::pf4::Pf4Config::new(
&require_macro(¯os, "P", program)?,
¯o_or(¯os, "H", ""),
&require_macro(¯os, "B", program)?,
);
let db = db.clone();
handle.spawn(async move {
if let Err(e) = crate::snl::pf4::run(config, db).await {
eprintln!("pf4 error: {e}");
}
});
}
"Io" => {
let config = crate::snl::io::IoConfig::new(
&require_macro(¯os, "P", program)?,
¯o_or(¯os, "MONO", ""),
¯o_or(¯os, "VSC", ""),
);
handle.spawn(async move {
if let Err(e) = crate::snl::io::run(config).await {
eprintln!("Io error: {e}");
}
});
}
"flexCombinedMotion" => {
let config = crate::snl::flex_combined_motion::FlexConfig::new(
&require_macro(¯os, "P", program)?,
&require_macro(¯os, "M", program)?,
¯o_or(¯os, "CAP", ""),
&require_macro(¯os, "FM", program)?,
&require_macro(¯os, "CM", program)?,
);
handle.spawn(async move {
if let Err(e) = crate::snl::flex_combined_motion::run(config).await {
eprintln!("flexCombinedMotion error: {e}");
}
});
}
_ => {
return Err(format!(
"unknown program '{program}'. Available: kohzuCtl, kohzuCtl_soft, hrCtl, ml_monoCtl, orient, filterDrive, pf4, Io, flexCombinedMotion"
));
}
}
println!("seq {program} started with macros: {macro_str}");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_macros() {
let m = parse_macros("P=mini:,M_THETA=dcm:theta, M_Y = dcm:y");
assert_eq!(m.get("P").unwrap(), "mini:");
assert_eq!(m.get("M_THETA").unwrap(), "dcm:theta");
assert_eq!(m.get("M_Y").unwrap(), "dcm:y");
}
#[test]
fn test_parse_macros_empty() {
let m = parse_macros("");
assert!(m.is_empty());
}
#[test]
fn test_unknown_program() {
let rt = tokio::runtime::Runtime::new().unwrap();
let db = PvDatabase::new();
let result = seq_start("nonexistent", "P=x:", rt.handle(), &db);
assert!(result.is_err());
assert!(result.unwrap_err().contains("unknown program"));
}
}