use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayDoState {
Init,
Disable,
MaybeStandby,
Idle,
Standby,
MaybeWait,
Active,
Waiting,
Action,
}
impl DelayDoState {
pub fn is_transient(self) -> bool {
matches!(
self,
DelayDoState::Init
| DelayDoState::MaybeStandby
| DelayDoState::MaybeWait
| DelayDoState::Action
)
}
pub fn is_published(self) -> bool {
!matches!(self, DelayDoState::MaybeStandby | DelayDoState::MaybeWait)
}
}
impl std::fmt::Display for DelayDoState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DelayDoState::Init => write!(f, "init"),
DelayDoState::Disable => write!(f, "disable"),
DelayDoState::MaybeStandby => write!(f, "maybeStandby"),
DelayDoState::Idle => write!(f, "idle"),
DelayDoState::Standby => write!(f, "standby"),
DelayDoState::MaybeWait => write!(f, "maybeWait"),
DelayDoState::Active => write!(f, "active"),
DelayDoState::Waiting => write!(f, "waiting"),
DelayDoState::Action => write!(f, "action"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DelayDoInputs {
pub enable: bool,
pub enable_changed: bool,
pub standby: bool,
pub standby_changed: bool,
pub active: bool,
pub active_changed: bool,
}
#[derive(Debug, Clone, Copy, Default)]
struct EventFlag(bool);
impl EventFlag {
fn sync(&mut self, monitor_event: bool) {
self.0 |= monitor_event;
}
fn test(self) -> bool {
self.0
}
fn test_and_clear(&mut self) -> bool {
std::mem::replace(&mut self.0, false)
}
fn clear(&mut self) {
self.0 = false;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayDoAction {
None,
ProcessAction,
}
pub struct DelayDoController {
pub state: DelayDoState,
pub delay_period: Duration,
resume_waiting: bool,
enable_mon: EventFlag,
standby_mon: EventFlag,
active_mon: EventFlag,
wait: Option<(Instant, Duration)>,
}
impl Default for DelayDoController {
fn default() -> Self {
Self {
state: DelayDoState::Init,
delay_period: Duration::from_secs(0),
resume_waiting: false,
enable_mon: EventFlag::default(),
standby_mon: EventFlag::default(),
active_mon: EventFlag::default(),
wait: None,
}
}
}
impl DelayDoController {
pub fn new(delay_secs: f64) -> Self {
Self {
delay_period: epics_base_rs::runtime::time::duration_from_secs(delay_secs),
..Default::default()
}
}
pub fn step(&mut self, inputs: &DelayDoInputs) -> (DelayDoAction, DelayDoState) {
let action;
self.enable_mon.sync(inputs.enable_changed);
self.standby_mon.sync(inputs.standby_changed);
self.active_mon.sync(inputs.active_changed);
match self.state {
DelayDoState::Init => {
action = DelayDoAction::None;
self.resume_waiting = false;
self.state = DelayDoState::Idle;
}
DelayDoState::Disable => {
action = DelayDoAction::None;
if self.enable_mon.test_and_clear() && inputs.enable {
self.active_mon.clear();
self.state = DelayDoState::MaybeStandby;
}
}
DelayDoState::MaybeStandby => {
action = DelayDoAction::None;
if inputs.standby {
self.state = DelayDoState::Standby;
} else if inputs.active {
self.state = DelayDoState::Active;
} else {
self.state = DelayDoState::Idle;
}
}
DelayDoState::Idle => {
action = DelayDoAction::None;
if self.enable_mon.test_and_clear() && !inputs.enable {
self.state = DelayDoState::Disable;
} else if self.standby_mon.test_and_clear() && inputs.standby {
self.state = DelayDoState::Standby;
} else if self.active_mon.test_and_clear() && inputs.active {
self.state = DelayDoState::Active;
}
}
DelayDoState::Standby => {
action = DelayDoAction::None;
if self.enable_mon.test_and_clear() && !inputs.enable {
self.resume_waiting = false;
self.state = DelayDoState::Disable;
} else if self.standby_mon.test_and_clear() && !inputs.standby {
self.state = DelayDoState::MaybeWait;
}
}
DelayDoState::MaybeWait => {
action = DelayDoAction::None;
if inputs.active {
self.state = DelayDoState::Active;
} else if self.active_mon.test() || self.resume_waiting {
self.active_mon.clear();
self.wait = Some((Instant::now(), self.delay_period));
self.state = DelayDoState::Waiting;
} else {
self.state = DelayDoState::Idle;
}
}
DelayDoState::Active => {
action = DelayDoAction::None;
if self.enable_mon.test_and_clear() && !inputs.enable {
self.state = DelayDoState::Disable;
} else if self.standby_mon.test_and_clear() && inputs.standby {
self.state = DelayDoState::Standby;
} else if self.active_mon.test_and_clear() && !inputs.active {
self.wait = Some((Instant::now(), self.delay_period));
self.state = DelayDoState::Waiting;
}
}
DelayDoState::Waiting => {
if self.enable_mon.test_and_clear() && !inputs.enable {
action = DelayDoAction::None;
self.state = DelayDoState::Disable;
self.wait = None;
} else if self.standby_mon.test_and_clear() && inputs.standby {
action = DelayDoAction::None;
self.resume_waiting = true;
self.state = DelayDoState::Standby;
self.wait = None;
} else if self.active_mon.test_and_clear() && inputs.active {
action = DelayDoAction::None;
self.state = DelayDoState::Active;
self.wait = None;
} else if let Some((start, period)) = self.wait {
if start.elapsed() >= period {
self.resume_waiting = false;
self.wait = None;
self.state = DelayDoState::Action;
action = DelayDoAction::None;
} else {
action = DelayDoAction::None;
}
} else {
action = DelayDoAction::None;
}
}
DelayDoState::Action => {
action = DelayDoAction::ProcessAction;
self.state = DelayDoState::Idle;
}
}
(action, self.state)
}
pub fn delay_remaining(&self) -> Option<Duration> {
let (start, period) = self.wait?;
Some(period.saturating_sub(start.elapsed()))
}
}
use epics_base_rs::server::database::PvDatabase;
use epics_base_rs::server::database::db_access::{DbChannel, DbMultiMonitor, alloc_origin};
#[derive(Debug, Clone)]
pub struct DelayDoConfig {
pub prefix: String,
pub record: String,
}
impl DelayDoConfig {
pub fn new(prefix: &str, record: &str) -> Self {
Self {
prefix: prefix.to_string(),
record: record.to_string(),
}
}
pub fn pv(&self, leaf: &str) -> String {
format!("{}{}:{}", self.prefix, self.record, leaf)
}
}
fn debug_print(debug_flag: i32, level: i32, msg: &str) {
if debug_flag >= level {
println!("<delayDo.st,{level},delayDo> {msg}");
}
}
pub async fn run(
config: DelayDoConfig,
db: PvDatabase,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let origin = alloc_origin();
let pv_enable = config.pv("enable");
let pv_standby = config.pv("standbyCalc");
let pv_active = config.pv("activeCalc");
let pv_delay = config.pv("delay");
let pv_debug = config.pv("debug");
let monitored = vec![
pv_enable.clone(),
pv_standby.clone(),
pv_active.clone(),
pv_delay.clone(),
pv_debug.clone(),
];
let ch_enable = DbChannel::with_origin(&db, &pv_enable, origin);
let ch_standby = DbChannel::with_origin(&db, &pv_standby, origin);
let ch_active = DbChannel::with_origin(&db, &pv_active, origin);
let ch_delay = DbChannel::with_origin(&db, &pv_delay, origin);
let ch_debug = DbChannel::with_origin(&db, &pv_debug, origin);
let ch_state = DbChannel::with_origin(&db, &config.pv("state"), origin);
let ch_doseq = DbChannel::with_origin(&db, &format!("{}.PROC", config.pv("doSeq")), origin);
let mut monitor = DbMultiMonitor::new_filtered(&db, &monitored, origin).await;
if monitor.sub_count() != monitored.len() {
return Err(format!(
"delayDo: {} of the {} PVs it assigns are not in the database ({})",
monitored.len() - monitor.sub_count(),
monitored.len(),
monitored.join(", ")
)
.into());
}
let mut debug_flag = ch_debug.get_i32().await;
let mut ctrl = DelayDoController::new(ch_delay.get_f64().await);
let mut inputs = DelayDoInputs {
enable: ch_enable.get_i16().await != 0,
enable_changed: false,
standby: ch_standby.get_i32().await != 0,
standby_changed: false,
active: ch_active.get_i32().await != 0,
active_changed: false,
};
loop {
loop {
let previous = ctrl.state;
let (action, state) = ctrl.step(&inputs);
inputs.enable_changed = false;
inputs.standby_changed = false;
inputs.active_changed = false;
if action == DelayDoAction::ProcessAction {
let _ = ch_doseq.put_i32_process(1).await;
}
if state != previous {
debug_print(debug_flag, 3, &format!("{previous} -> {state}"));
if state.is_published() {
let _ = ch_state.put_string_process(&state.to_string()).await;
}
}
if !state.is_transient() {
break;
}
}
loop {
let woken = match ctrl.delay_remaining() {
Some(remaining) => tokio::time::timeout(remaining, monitor.wait_change())
.await
.ok(),
None => Some(monitor.wait_change().await),
};
let Some((pv, value)) = woken else {
break;
};
if pv == pv_enable {
inputs.enable = (value as i16) != 0;
inputs.enable_changed = true;
break;
} else if pv == pv_standby {
inputs.standby = (value as i32) != 0;
inputs.standby_changed = true;
break;
} else if pv == pv_active {
inputs.active = (value as i32) != 0;
inputs.active_changed = true;
break;
} else if pv == pv_delay {
ctrl.delay_period = epics_base_rs::runtime::time::duration_from_secs(value);
} else if pv == pv_debug {
debug_flag = value as i32;
}
}
}
}