use std::sync::Arc;
use crate::error::{CaError, CaResult};
use crate::runtime::log::{ErrlogSevEnum, errlog_sev_printf};
use crate::server::database::filters::sync::{DbState, db_state_registry};
use crate::server::device_support::{DeviceReadOutcome, DeviceSupport};
use crate::server::record::Record;
use crate::types::EpicsValue;
pub struct DbStateDeviceSupport {
inp: String,
out: String,
state: Option<Arc<DbState>>,
}
impl DbStateDeviceSupport {
pub fn new(inp: &str, out: &str) -> Self {
Self {
inp: inp.to_string(),
out: out.to_string(),
state: None,
}
}
}
impl DeviceSupport for DbStateDeviceSupport {
fn dtyp(&self) -> &str {
"Db State"
}
fn init(&mut self, record: &mut dyn Record) -> CaResult<()> {
let rt = record.record_type();
let raw = match rt {
"bi" => self.inp.as_str(),
"bo" => self.out.as_str(),
other => {
return Err(CaError::InvalidValue(format!(
"DTYP='Db State': unsupported record type '{other}' (use bi or bo)"
)));
}
};
let name = raw.strip_prefix('@').unwrap_or(raw);
if name.is_empty() {
self.state = None;
return Ok(());
}
let registry = db_state_registry();
self.state = Some(match registry.find(name) {
Some(s) => s,
None => {
let devsup = if rt == "bi" {
"devBiDbState"
} else {
"devBoDbState"
};
errlog_sev_printf(
ErrlogSevEnum::Info,
&format!("{devsup}: Creating new db state '{name}'"),
);
registry.get_or_create(name)
}
});
Ok(())
}
fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
if let Some(state) = &self.state {
let bit = u16::from(state.get());
record.put_field("VAL", EpicsValue::Enum(bit))?;
}
Ok(DeviceReadOutcome::computed())
}
fn write(&mut self, record: &mut dyn Record) -> CaResult<()> {
if let Some(state) = &self.state {
let on = match record.get_field("VAL") {
Some(EpicsValue::Enum(v)) | Some(EpicsValue::UShort(v)) => v != 0,
Some(EpicsValue::Short(v)) => v != 0,
Some(EpicsValue::Long(v)) => v != 0,
_ => false,
};
state.set(on);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::records::ai::AiRecord;
use crate::server::records::bi::BiRecord;
use crate::server::records::bo::BoRecord;
#[test]
fn init_resolves_bo_state_from_out_and_write_sets_it() {
let mut dev = DbStateDeviceSupport::new("", "@DBSTATE_TEST_BO");
let mut rec = BoRecord::new(1);
dev.init(&mut rec).unwrap();
dev.write(&mut rec).unwrap();
assert!(db_state_registry().get("DBSTATE_TEST_BO"));
let mut rec0 = BoRecord::new(0);
dev.write(&mut rec0).unwrap();
assert!(!db_state_registry().get("DBSTATE_TEST_BO"));
}
#[test]
fn init_resolves_bi_state_from_inp_and_read_loads_it() {
db_state_registry().set("DBSTATE_TEST_BI", true);
let mut dev = DbStateDeviceSupport::new("@DBSTATE_TEST_BI", "");
let mut rec = BiRecord::new(0);
dev.init(&mut rec).unwrap();
let outcome = dev.read(&mut rec).unwrap();
assert!(outcome.did_compute);
assert_eq!(rec.get_field("VAL"), Some(EpicsValue::Enum(1)));
}
#[test]
fn init_rejects_unsupported_record_type() {
let mut dev = DbStateDeviceSupport::new("@X", "");
let mut rec = AiRecord::new(0.0);
assert!(dev.init(&mut rec).is_err());
}
#[test]
fn empty_instio_name_is_noop() {
let mut dev = DbStateDeviceSupport::new("@", "");
let mut rec = BiRecord::new(7);
dev.init(&mut rec).unwrap();
assert!(dev.state.is_none());
dev.read(&mut rec).unwrap();
assert_eq!(rec.get_field("VAL"), Some(EpicsValue::Enum(7)));
}
}