1mod cam;
5pub mod image;
6
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10
11use asyn_rs::interfaces::motor::AsynMotor;
12use epics_base_rs::error::{CaError, CaResult};
13use epics_base_rs::server::database::db_access::DbChannel;
14use epics_base_rs::server::device_support::DeviceSupport;
15use epics_base_rs::server::records::busy::BusyRecord;
16use epics_base_rs::server::scan::ScanOwner;
17use epics_ca_rs::server::CaServer;
18use epics_pva_rs::server::native_source::PvDatabaseSource;
19use epics_pva_rs::server_native::{CompositeSource, PvaServer, PvaServerConfig, SharedSource};
20use motor_rs::poll_loop::PollCommand;
21use motor_rs::sim_motor::SimMotor;
22use motor_rs::{MotorBuilder, MotorRecord};
23
24const EA_DB: &str = include_str!("db/ea.db");
25const MOTOR_DB: &str = include_str!("db/motor.db");
26const CAM_DB: &str = include_str!("db/cam.db");
27const PLUGIN_DB: &str = include_str!("db/plugin.db");
28
29pub const MOTOR_P: &str = "BL01T-MO-SIMC-01:";
30pub const CAM_P: &str = "BL01T-DI-CAM-01";
31const AXES: [(&str, &str); 2] = [("M1", "Motor 1"), ("M2", "Motor 2")];
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Part {
35 Ea,
36 Motor,
37 Cam,
38}
39
40impl Part {
41 pub const ALL: [Part; 3] = [Part::Ea, Part::Motor, Part::Cam];
42
43 pub fn parse(s: &str) -> Option<Part> {
44 match s {
45 "ea" => Some(Part::Ea),
46 "motor" => Some(Part::Motor),
47 "cam" => Some(Part::Cam),
48 _ => None,
49 }
50 }
51
52 pub fn describe(self) -> &'static str {
53 match self {
54 Part::Ea => "bl01t:A, bl01t:B, bl01t:SUM",
55 Part::Motor => "BL01T-MO-SIMC-01:M1, :M2",
56 Part::Cam => "BL01T-DI-CAM-01:DET:, :ARR:, :PVA:",
57 }
58 }
59}
60
61pub struct Ioc {
64 pub server: Arc<CaServer>,
65 pub pva: PvaServer,
66 _scan: ScanOwner,
67}
68
69impl Ioc {
70 pub fn pva_ports(&self) -> (u16, u16) {
72 (self.pva.tcp_addr().port(), self.pva.config().udp_port)
73 }
74
75 pub async fn run(self) -> Result<(), String> {
77 let Ioc { server, pva, _scan } = self;
78 tokio::select! {
79 r = server.run() => r.map_err(|e| format!("CA: {e}")),
80 r = pva.run() => r.map_err(|e| format!("PVA: {e}")),
81 }
82 }
83}
84
85fn macros(pairs: &[(&str, &str)]) -> HashMap<String, String> {
86 pairs
87 .iter()
88 .map(|(k, v)| (k.to_string(), v.to_string()))
89 .collect()
90}
91
92pub async fn build(parts: &[Part], ca_port: Option<u16>, pva_port: Option<u16>) -> CaResult<Ioc> {
97 let mut b = CaServer::builder();
98 if let Some(port) = ca_port {
99 b = b.port(port);
100 }
101 let mut poll_txs = Vec::new();
102
103 if parts.contains(&Part::Ea) {
104 b = b.db_string(EA_DB, &HashMap::new())?;
105 }
106 if parts.contains(&Part::Motor) {
107 b = b.register_record_type("motor", || Box::new(MotorRecord::default()));
108 for (m, desc) in AXES {
109 let sim: Arc<Mutex<dyn AsynMotor>> =
110 Arc::new(Mutex::new(SimMotor::new().with_limits(-110.0, 110.0)));
111 let setup = MotorBuilder::new(sim)
112 .moving_poll_interval(Duration::from_millis(100))
113 .idle_poll_interval(Duration::from_secs(1))
114 .build();
115 let dtyp = format!("simMotor_{m}");
116 let ds = Mutex::new(Some(setup.device_support.with_dtyp_name(dtyp.clone())));
117 tokio::spawn(setup.poll_loop.run());
118 poll_txs.push(setup.poll_cmd_tx);
119 b = b.register_device_support(&dtyp, move || {
120 let ds = ds.lock().unwrap().take();
121 Box::new(ds.expect("one record per sim axis")) as Box<dyn DeviceSupport>
122 });
123 b = b.db_string(
124 MOTOR_DB,
125 ¯os(&[("P", MOTOR_P), ("M", m), ("DESC", desc)]),
126 )?;
127 }
128 }
129 if parts.contains(&Part::Cam) {
130 b = b.register_record_type("busy", || Box::new(BusyRecord::new()));
131 b = b.db_string(CAM_DB, ¯os(&[("P", CAM_P), ("R", ":DET:")]))?;
132 for r in cam::PLUGINS {
133 b = b.db_string(PLUGIN_DB, ¯os(&[("P", CAM_P), ("R", r)]))?;
134 }
135 b = b.db_string(cam::PVA_NAME_DB, ¯os(&[("P", CAM_P)]))?;
136 }
137
138 let server = Arc::new(b.build().await?);
139 let db = server.database().clone();
140 db.initialize_link_locality().await;
142 db.setup_cp_links().await;
143 let scan = ScanOwner::start(db.clone());
144
145 if parts.contains(&Part::Ea) {
146 DbChannel::new(&db, "bl01t:A").put_f64_process(2.54).await?;
148 DbChannel::new(&db, "bl01t:B").put_f64_process(2.61).await?;
149 }
150 for tx in poll_txs {
151 let _ = tx.send(PollCommand::StartPolling).await;
152 }
153
154 let source = CompositeSource::new();
156 let records = Arc::new(PvDatabaseSource::new(db.clone()));
157 source
158 .add_source("records", records, 0)
159 .map_err(CaError::InvalidValue)?;
160 if parts.contains(&Part::Cam) {
161 let output = image::open_pv();
162 let shared = Arc::new(SharedSource::new());
163 shared.add(format!("{CAM_P}:PVA:OUTPUT"), output.clone());
164 source
165 .add_source("cam", shared, 1)
166 .map_err(CaError::InvalidValue)?;
167 cam::start(&db, output).await?;
168 }
169 let mut config = PvaServerConfig::default().with_env();
170 if let Some(port) = pva_port {
171 config.tcp_port = port;
172 config.udp_port = if port == 0 { 0 } else { port + 1 };
173 }
174 let pva = PvaServer::start(source, config).map_err(|e| CaError::InvalidValue(e.to_string()))?;
175
176 Ok(Ioc {
177 server,
178 pva,
179 _scan: scan,
180 })
181}