pub const POWERS: [u32; 16] = [5, 6, 7, 8, 9, 10, 11, 0, 3, 4, 5, 6, 7, 8, 9, 0];
pub const MIN_GAIN: i32 = 0;
pub const MAX_GAIN: i32 = 15;
pub const UNUSED_GAIN: i32 = 7;
pub fn bits_to_gain_index(g1: bool, g2: bool, g3: bool, no: bool) -> i32 {
let t0 = g1 as i32;
let t1 = g2 as i32;
let t2 = g3 as i32;
let tx = no as i32;
(tx << 3) | (t2 << 2) | (t1 << 1) | t0
}
pub fn gain_index_to_bits(idx: i32) -> (bool, bool, bool, bool) {
let g1 = (idx & 1) != 0;
let g2 = (idx & 2) != 0;
let g3 = (idx & 4) != 0;
let no = (idx & 8) != 0;
(g1, g2, g3, no)
}
pub fn is_valid_gain_index(idx: i32) -> bool {
(MIN_GAIN..MAX_GAIN).contains(&idx) && idx != UNUSED_GAIN
}
pub fn gain_for_index(idx: i32) -> f64 {
if !(0..16).contains(&idx) {
return 0.0;
}
10.0_f64.powi(POWERS[idx as usize] as i32)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FemtoState {
Init,
Idle,
ChangeGain,
UpdateGain,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct FemtoPuts {
pub gain_index: Option<i32>,
pub gain: Option<f64>,
pub bits: Option<(bool, bool, bool, bool)>,
}
impl FemtoState {
pub fn is_transient(self) -> bool {
!matches!(self, FemtoState::Idle)
}
}
pub struct FemtoController {
pub state: FemtoState,
pub gain_index: i32,
pub current_gain: i32,
pub g1: bool,
pub g2: bool,
pub g3: bool,
pub no: bool,
pub gain: f64,
}
impl Default for FemtoController {
fn default() -> Self {
Self {
state: FemtoState::Init,
gain_index: 0,
current_gain: -1,
g1: false,
g2: false,
g3: false,
no: false,
gain: 0.0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum FemtoEvent {
BitsChanged {
g1: bool,
g2: bool,
g3: bool,
no: bool,
},
GainIndexChanged(i32),
}
impl FemtoController {
pub fn step(&mut self, event: Option<FemtoEvent>) -> (FemtoState, FemtoPuts) {
let mut puts = FemtoPuts::default();
match self.state {
FemtoState::Init => {
if let Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) = event {
self.g1 = g1;
self.g2 = g2;
self.g3 = g3;
self.no = no;
}
let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
self.gain_index = if !self.g1 && !self.g2 && !self.g3 && !self.no {
8 } else if !is_valid_gain_index(idx) {
6 } else {
idx
};
self.current_gain = -1;
self.gain = gain_for_index(self.gain_index);
puts.gain_index = Some(self.gain_index);
puts.gain = Some(self.gain);
self.state = FemtoState::ChangeGain;
}
FemtoState::Idle => match event {
Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) => {
self.g1 = g1;
self.g2 = g2;
self.g3 = g3;
self.no = no;
self.state = FemtoState::UpdateGain;
}
Some(FemtoEvent::GainIndexChanged(idx)) => {
self.gain_index = idx;
self.state = FemtoState::ChangeGain;
}
None => {}
},
FemtoState::ChangeGain => {
if self.current_gain == self.gain_index || !is_valid_gain_index(self.gain_index) {
if self.current_gain >= 0 && self.current_gain != self.gain_index {
self.gain_index = self.current_gain;
self.gain = gain_for_index(self.current_gain);
puts.gain_index = Some(self.gain_index);
puts.gain = Some(self.gain);
}
self.state = FemtoState::Idle;
} else {
let (g1, g2, g3, no) = gain_index_to_bits(self.gain_index);
self.g1 = g1;
self.g2 = g2;
self.g3 = g3;
self.no = no;
self.current_gain = self.gain_index;
self.gain = gain_for_index(self.gain_index);
puts.bits = Some((g1, g2, g3, no));
puts.gain = Some(self.gain);
self.state = FemtoState::Idle;
}
}
FemtoState::UpdateGain => {
let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
self.gain_index = if !is_valid_gain_index(idx) { 6 } else { idx };
self.current_gain = self.gain_index;
self.gain = gain_for_index(self.gain_index);
puts.gain_index = Some(self.gain_index);
puts.gain = Some(self.gain);
self.state = FemtoState::Idle;
}
}
(self.state, puts)
}
}
use epics_base_rs::server::database::PvDatabase;
use epics_base_rs::server::database::db_access::{DbChannel, DbMultiMonitor, alloc_origin};
#[derive(Debug, Clone)]
pub struct FemtoConfig {
pub prefix: String,
pub hardware: String,
pub function: String,
pub gain_bit_pvs: [String; 3],
pub noise_bit_pv: String,
}
impl FemtoConfig {
pub fn new(p: &str, h: &str, f: &str, g1: &str, g2: &str, g3: &str, no: &str) -> Self {
Self {
prefix: p.to_string(),
hardware: h.to_string(),
function: f.to_string(),
gain_bit_pvs: [g1.to_string(), g2.to_string(), g3.to_string()],
noise_bit_pv: no.to_string(),
}
}
pub fn pv(&self, leaf: &str) -> String {
format!("{}{}{}{}", self.prefix, self.hardware, self.function, leaf)
}
}
fn debug_print(debug_flag: i32, level: i32, msg: &str) {
if debug_flag >= level {
println!("<femto.st,{level},femto> {msg}");
}
}
pub async fn run(
config: FemtoConfig,
db: PvDatabase,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let origin = alloc_origin();
let pv_t0 = config.gain_bit_pvs[0].clone();
let pv_t1 = config.gain_bit_pvs[1].clone();
let pv_t2 = config.gain_bit_pvs[2].clone();
let pv_tx = config.noise_bit_pv.clone();
let pv_gainidx = config.pv("gainidx");
let pv_debug = config.pv("debug");
let ch_gain = DbChannel::with_origin(&db, &config.pv("gain"), origin);
let ch_t0 = DbChannel::with_origin(&db, &pv_t0, origin);
let ch_t1 = DbChannel::with_origin(&db, &pv_t1, origin);
let ch_t2 = DbChannel::with_origin(&db, &pv_t2, origin);
let ch_tx = DbChannel::with_origin(&db, &pv_tx, origin);
let ch_gainidx = DbChannel::with_origin(&db, &pv_gainidx, origin);
let ch_debug = DbChannel::with_origin(&db, &pv_debug, origin);
let monitored = vec![
pv_t0.clone(),
pv_t1.clone(),
pv_t2.clone(),
pv_tx.clone(),
pv_gainidx.clone(),
pv_debug.clone(),
];
let mut monitor = DbMultiMonitor::new_filtered(&db, &monitored, origin).await;
if monitor.sub_count() != monitored.len() {
return Err(format!(
"femto: {} 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 = FemtoController::default();
let mut event = Some(FemtoEvent::BitsChanged {
g1: ch_t0.get_i32().await != 0,
g2: ch_t1.get_i32().await != 0,
g3: ch_t2.get_i32().await != 0,
no: ch_tx.get_i32().await != 0,
});
loop {
loop {
let previous = ctrl.state;
let (state, puts) = ctrl.step(event.take());
if let Some((g1, g2, g3, no)) = puts.bits {
let _ = ch_t0.put_i32_process(g1 as i32).await;
let _ = ch_t1.put_i32_process(g2 as i32).await;
let _ = ch_t2.put_i32_process(g3 as i32).await;
let _ = ch_tx.put_i32_process(no as i32).await;
}
if let Some(idx) = puts.gain_index {
let _ = ch_gainidx.put_i32_process(idx).await;
}
if let Some(gain) = puts.gain {
let _ = ch_gain.put_f64_process(gain).await;
}
if state != previous {
debug_print(debug_flag, 2, &format!("{previous:?} -> {state:?}"));
}
if !state.is_transient() {
break;
}
}
loop {
let (pv, value) = monitor.wait_change().await;
if pv == pv_debug {
debug_flag = value as i32;
debug_print(
debug_flag,
1,
&format!("Debug level changed to {debug_flag}"),
);
continue;
}
if pv == pv_gainidx {
event = Some(FemtoEvent::GainIndexChanged(value as i32));
} else {
let mut g1 = ctrl.g1;
let mut g2 = ctrl.g2;
let mut g3 = ctrl.g3;
let mut no = ctrl.no;
let level = (value as i32) != 0;
if pv == pv_t0 {
g1 = level;
} else if pv == pv_t1 {
g2 = level;
} else if pv == pv_t2 {
g3 = level;
} else if pv == pv_tx {
no = level;
}
event = Some(FemtoEvent::BitsChanged { g1, g2, g3, no });
}
break;
}
}
}