use epics_base_rs::server::database::LinkSet;
use epics_base_rs::server::iocsh::registry::{
ArgDesc, ArgType, ArgValue, CommandContext, CommandDef, CommandOutcome,
};
use super::resolver::CaLinkResolver;
pub fn ca_caxr_command(resolver: CaLinkResolver) -> CommandDef {
CommandDef::new(
"caxr",
vec![ArgDesc {
name: "pv_name",
arg_type: ArgType::String,
optional: false,
}],
"caxr <pv_name>",
move |args: &[ArgValue], ctx: &CommandContext| {
let name = match args.first() {
Some(ArgValue::String(s)) => s.clone(),
_ => return Err("caxr: missing pv_name".into()),
};
let resolver = resolver.clone();
let handle = ctx.runtime_handle().clone();
let result = std::thread::spawn(move || {
handle.block_on(async move { resolver.open(&name).await })
})
.join();
match result {
Ok(Ok(_link)) => {
ctx.println("caxr: opened (monitor active)");
Ok(CommandOutcome::Continue)
}
Ok(Err(e)) => Err(format!("caxr: open failed: {e}")),
Err(_) => Err("caxr: panic in runtime thread".into()),
}
},
)
}
pub fn db_dbcaxr_command(resolver: CaLinkResolver) -> CommandDef {
CommandDef::new(
"dbcaxr",
vec![ArgDesc {
name: "record",
arg_type: ArgType::String,
optional: true,
}],
"dbcaxr [<recordName>]",
move |args: &[ArgValue], ctx: &CommandContext| {
let target = match args.first() {
Some(ArgValue::String(s)) if !s.is_empty() => Some(s.clone()),
_ => None,
};
ctx.println(&format!(
"dbcaxr: {} cached CA link(s)",
resolver.link_count()
));
if let Some(rec) = target {
let db = ctx.db().clone();
let handle = ctx.runtime_handle().clone();
let rec_clone = rec.clone();
let links = std::thread::spawn(move || {
handle.block_on(async move { db.record_link_fields(&rec_clone).await })
})
.join()
.unwrap_or_default();
if links.is_empty() {
ctx.println(&format!(
" '{rec}': no link fields found (or record missing)"
));
} else {
ctx.println(&format!(" '{rec}': {} link field(s)", links.len()));
for (field, raw, parsed) in links {
if let epics_base_rs::server::record::ParsedLink::Ca(name) = parsed {
let connected =
<CaLinkResolver as LinkSet>::is_connected(&resolver, &name);
let value = <CaLinkResolver as LinkSet>::get_value(&resolver, &name);
let alarm =
<CaLinkResolver as LinkSet>::alarm_severity(&resolver, &name);
let ts = <CaLinkResolver as LinkSet>::time_stamp(&resolver, &name);
ctx.println(&format!(
" {field}={raw:?} ca://{name} connected={connected}"
));
if let Some(v) = value {
ctx.println(&format!(" value={v}"));
}
if let Some(sev) = alarm {
ctx.println(&format!(" alarmSeverity={sev}"));
}
if let Some((s, n)) = ts {
ctx.println(&format!(" timeStamp={s}.{n:09}"));
}
}
}
}
}
Ok(CommandOutcome::Continue)
},
)
}
pub fn register_calink_commands(resolver: CaLinkResolver) -> Vec<CommandDef> {
vec![
ca_caxr_command(resolver.clone()),
db_dbcaxr_command(resolver),
]
}
#[cfg(test)]
mod tests {
use super::*;
async fn dummy_resolver() -> CaLinkResolver {
CaLinkResolver::new(tokio::runtime::Handle::current())
.await
.expect("CA client init")
}
#[tokio::test(flavor = "multi_thread")]
async fn register_calink_commands_returns_two() {
let r = dummy_resolver().await;
let cmds = register_calink_commands(r);
assert_eq!(cmds.len(), 2);
let names: Vec<&str> = cmds.iter().map(|c| c.name.as_str()).collect();
assert!(names.contains(&"caxr"));
assert!(names.contains(&"dbcaxr"));
}
}