Skip to main content

std_rs/
seq_runner.rs

1//! SNL program launcher for the std module — the Rust stand-in for C's
2//! `seq &program, "macros"`.
3//!
4//! Each `snl` module here is a pure state machine plus a `run(config, db)`
5//! that gives it PVs; this is the one place that turns a program name and a
6//! macro string into the right pair. An IOC binary reaches it with a single
7//! `seqStart` startup command, the same shape
8//! `optics_rs::seq_runner::seq_start` is registered with.
9
10use std::collections::HashMap;
11
12use epics_base_rs::server::database::PvDatabase;
13use epics_base_rs::server::iocsh::macro_defn_pairs;
14
15/// Split a `seq` macro string into definitions.
16///
17/// Through `macro_defn_pairs`, the port's owner of C's `macParseDefns`
18/// grammar (`macUtil.c`), so a quoted value with an embedded comma survives —
19/// a raw `split(',')` tears it. A name given with no `=` is a deletion in
20/// that grammar and is dropped here, since a program can only be started with
21/// definitions.
22pub fn parse_macros(input: &str) -> HashMap<String, String> {
23    macro_defn_pairs(input)
24        .into_iter()
25        .filter_map(|(name, value)| value.map(|v| (name, v)))
26        .collect()
27}
28
29fn require_macro(
30    macros: &HashMap<String, String>,
31    key: &str,
32    program: &str,
33) -> Result<String, String> {
34    macros
35        .get(key)
36        .cloned()
37        .ok_or_else(|| format!("{program}: required macro '{key}' not specified"))
38}
39
40/// Start a std-module SNL program by name.
41///
42/// | Name | Macros | Program |
43/// |------|--------|---------|
44/// | `delayDo` | P, R | `delayDo.st` — wait out an active condition, then process `doSeq` |
45/// | `femto` | P, H, F, G1, G2, G3, NO | `femto.st` — Femto amplifier gain control |
46///
47/// Must be called with the runtime reachable — from st.cmd that means
48/// `CommandContext::bridge()`, because the shell runs on a blocking thread.
49pub fn seq_start(
50    program: &str,
51    macro_str: &str,
52    bridge: &epics_base_rs::runtime::task::BlockingBridge,
53    db: &PvDatabase,
54) -> Result<(), String> {
55    let macros = parse_macros(macro_str);
56
57    match program {
58        "delayDo" => {
59            let config = crate::snl::delay_do::DelayDoConfig::new(
60                &require_macro(&macros, "P", program)?,
61                &require_macro(&macros, "R", program)?,
62            );
63            let db = db.clone();
64            bridge.spawn(async move {
65                if let Err(e) = crate::snl::delay_do::run(config, db).await {
66                    eprintln!("delayDo error: {e}");
67                }
68            });
69        }
70        "femto" => {
71            let config = crate::snl::femto::FemtoConfig::new(
72                &require_macro(&macros, "P", program)?,
73                &require_macro(&macros, "H", program)?,
74                &require_macro(&macros, "F", program)?,
75                &require_macro(&macros, "G1", program)?,
76                &require_macro(&macros, "G2", program)?,
77                &require_macro(&macros, "G3", program)?,
78                &require_macro(&macros, "NO", program)?,
79            );
80            let db = db.clone();
81            bridge.spawn(async move {
82                if let Err(e) = crate::snl::femto::run(config, db).await {
83                    eprintln!("femto error: {e}");
84                }
85            });
86        }
87        other => return Err(format!("seq_start: unknown std program '{other}'")),
88    }
89
90    Ok(())
91}