use std::sync::{Arc, Mutex};
use std::time::Instant;
use epics_base_rs::error::CaResult;
use epics_base_rs::server::device_support::{DeviceReadOutcome, DeviceSupport};
use epics_base_rs::server::record::Record;
use crate::records::epid::EpidRecord;
pub struct EpidFastDeviceSupport {
pvt: Arc<Mutex<EpidFastPvt>>,
}
pub struct EpidFastPvt {
pub kp: f64,
pub ki: f64,
pub kd: f64,
pub drvh: f64,
pub drvl: f64,
pub val: f64, pub fbon: bool,
pub cval: f64,
pub oval: f64,
pub err: f64,
pub p: f64,
pub i: f64,
pub d: f64,
pub dt: f64,
pub ct: Instant,
pub fbop: bool,
pub callback_interval: f64,
pub time_per_point_requested: f64,
pub time_per_point_actual: f64,
pub num_average: u32,
pub accumulated: f64,
pub count: u32,
pub output_writer: Option<Arc<Mutex<dyn FnMut(f64) + Send>>>,
pub output_reader: Option<Arc<Mutex<dyn FnMut() -> Option<f64> + Send>>>,
}
impl Default for EpidFastPvt {
fn default() -> Self {
let now = Instant::now();
Self {
kp: 1.0,
ki: 0.0,
kd: 0.0,
drvh: -1.0,
drvl: 1.0,
val: 0.0,
fbon: false,
cval: 0.0,
oval: 0.0,
err: 0.0,
p: 0.0,
i: 0.0,
d: 0.0,
dt: 0.0,
ct: now,
fbop: false,
callback_interval: 0.0,
time_per_point_requested: 0.0,
time_per_point_actual: 0.0,
num_average: 1,
accumulated: 0.0,
count: 0,
output_writer: None,
output_reader: None,
}
}
}
impl EpidFastPvt {
pub fn compute_num_average(&mut self) {
let n = if self.callback_interval > 0.0 {
(0.5 + self.time_per_point_requested / self.callback_interval) as i64
} else {
1
};
self.num_average = n.max(1) as u32;
self.time_per_point_actual = self.num_average as f64 * self.callback_interval;
}
pub fn interval_callback(&mut self, seconds: f64) {
self.callback_interval = seconds;
self.compute_num_average();
}
pub fn do_pid(&mut self, new_cval: f64) {
let cval = if self.num_average <= 1 {
new_cval
} else {
self.accumulated += new_cval;
self.count += 1;
if self.count < self.num_average {
return;
}
let avg = self.accumulated / self.count as f64;
self.accumulated = 0.0;
self.count = 0;
avg
};
self.cval = cval;
let dt = self.callback_interval;
self.dt = self.time_per_point_actual;
self.ct = Instant::now();
let ep = self.err;
let mut oval = self.oval;
let e = self.val - cval;
let de = e - ep;
self.p = self.kp * e;
let di = self.kp * self.ki * e * dt;
if self.fbon {
if !self.fbop {
self.i = match &self.output_reader {
Some(reader) => reader
.lock()
.ok()
.and_then(|mut r| r())
.unwrap_or(self.oval),
None => self.oval,
};
} else if (oval > self.drvl && oval < self.drvh)
|| (oval >= self.drvh && di < 0.0)
|| (oval <= self.drvl && di > 0.0)
{
self.i += di;
if self.i < self.drvl {
self.i = self.drvl;
}
if self.i > self.drvh {
self.i = self.drvh;
}
}
}
if self.ki == 0.0 {
self.i = 0.0;
}
self.d = if dt > 0.0 {
self.kp * self.kd * (de / dt)
} else {
0.0
};
self.err = e;
oval = self.p + self.i + self.d;
if oval > self.drvh {
oval = self.drvh;
}
if oval < self.drvl {
oval = self.drvl;
}
self.oval = oval;
self.fbop = self.fbon;
if self.fbon {
if let Some(ref writer) = self.output_writer {
if let Ok(mut w) = writer.lock() {
w(self.oval);
}
}
}
}
}
impl Default for EpidFastDeviceSupport {
fn default() -> Self {
Self::new()
}
}
impl EpidFastDeviceSupport {
pub fn new() -> Self {
Self {
pvt: Arc::new(Mutex::new(EpidFastPvt::default())),
}
}
pub fn pvt(&self) -> Arc<Mutex<EpidFastPvt>> {
Arc::clone(&self.pvt)
}
pub fn set_output_reader(&self, reader: Arc<Mutex<dyn FnMut() -> Option<f64> + Send>>) {
if let Ok(mut p) = self.pvt.lock() {
p.output_reader = Some(reader);
}
}
pub fn set_output_port(&self, sync_io: asyn_rs::sync_io::SyncIOHandle, reason: usize) {
let sync_io = Arc::new(sync_io);
let writer_io = Arc::clone(&sync_io);
let reader_io = Arc::clone(&sync_io);
let writer: Arc<Mutex<dyn FnMut(f64) + Send>> = Arc::new(Mutex::new(move |v: f64| {
let _ = writer_io.write_float64(reason, v);
}));
let reader: Arc<Mutex<dyn FnMut() -> Option<f64> + Send>> =
Arc::new(Mutex::new(move || reader_io.read_float64(reason).ok()));
if let Ok(mut p) = self.pvt.lock() {
p.output_writer = Some(writer);
p.output_reader = Some(reader);
}
}
pub fn start_callback_loop(
&self,
mut input_rx: tokio::sync::mpsc::Receiver<f64>,
output_fn: Arc<Mutex<dyn FnMut(f64) + Send>>,
) {
let pvt = Arc::clone(&self.pvt);
{
let mut p = pvt.lock().unwrap();
p.output_writer = Some(output_fn);
}
tokio::spawn(async move {
while let Some(new_cval) = input_rx.recv().await {
let mut p = pvt.lock().unwrap();
p.do_pid(new_cval);
}
});
}
pub fn start_callback_loop_with_port(
&self,
input_rx: tokio::sync::mpsc::Receiver<f64>,
output_sync_io: asyn_rs::sync_io::SyncIOHandle,
output_reason: usize,
) {
self.set_output_port(output_sync_io, output_reason);
let pvt = Arc::clone(&self.pvt);
let mut input_rx = input_rx;
tokio::spawn(async move {
while let Some(new_cval) = input_rx.recv().await {
let mut p = pvt.lock().unwrap();
p.do_pid(new_cval);
}
});
}
pub fn start_from_asyn_interrupts(
&self,
interrupt_rx: tokio::sync::broadcast::Receiver<asyn_rs::interrupt::InterruptValue>,
input_reason: usize,
output_fn: Arc<Mutex<dyn FnMut(f64) + Send>>,
) {
{
let mut p = self.pvt.lock().unwrap();
p.output_writer = Some(output_fn);
}
self.spawn_interrupt_loop(interrupt_rx, input_reason);
}
pub fn start_from_asyn_interrupts_with_port(
&self,
interrupt_rx: tokio::sync::broadcast::Receiver<asyn_rs::interrupt::InterruptValue>,
input_reason: usize,
output_sync_io: asyn_rs::sync_io::SyncIOHandle,
output_reason: usize,
) {
self.set_output_port(output_sync_io, output_reason);
self.spawn_interrupt_loop(interrupt_rx, input_reason);
}
fn spawn_interrupt_loop(
&self,
mut interrupt_rx: tokio::sync::broadcast::Receiver<asyn_rs::interrupt::InterruptValue>,
input_reason: usize,
) {
let pvt = Arc::clone(&self.pvt);
tokio::spawn(async move {
loop {
match interrupt_rx.recv().await {
Ok(iv) => {
if iv.reason == input_reason {
let v = match &iv.value {
asyn_rs::param::ParamValue::Float64(f) => Some(*f),
asyn_rs::param::ParamValue::Int32(i) => Some(*i as f64),
asyn_rs::param::ParamValue::Int64(i) => Some(*i as f64),
_ => None,
};
if let Some(v) = v {
let mut p = pvt.lock().unwrap();
p.do_pid(v);
}
}
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
}
}
}
});
}
fn update_params_from_record(&self, epid: &EpidRecord) {
let mut pvt = self.pvt.lock().unwrap();
if epid.dt != pvt.time_per_point_actual {
pvt.time_per_point_requested = epid.dt;
pvt.compute_num_average();
}
pvt.kp = epid.kp;
pvt.ki = epid.ki;
pvt.kd = epid.kd;
pvt.drvh = epid.drvh;
pvt.drvl = epid.drvl;
pvt.val = epid.val;
pvt.fbon = epid.fbon != 0;
}
fn update_record_from_params(&self, epid: &mut EpidRecord) {
let pvt = self.pvt.lock().unwrap();
epid.cval = pvt.cval;
epid.oval = pvt.oval;
epid.err = pvt.err;
epid.p = pvt.p;
epid.i = pvt.i;
epid.d = pvt.d;
epid.dt = pvt.dt;
epid.fbop = if pvt.fbop { 1 } else { 0 };
}
}
impl DeviceSupport for EpidFastDeviceSupport {
fn dtyp(&self) -> &str {
"Fast Epid"
}
fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
let epid = record
.as_any_mut()
.and_then(|a| a.downcast_mut::<EpidRecord>())
.expect("EpidFastDeviceSupport requires an EpidRecord");
self.update_params_from_record(epid);
self.update_record_from_params(epid);
Ok(DeviceReadOutcome::computed())
}
fn write(&mut self, _record: &mut dyn Record) -> CaResult<()> {
Ok(())
}
}