use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::time::SystemTime;
use crate::error::{AsynError, AsynResult, AsynStatus};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumEntry {
pub string: String,
pub value: i32,
pub severity: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterruptReason {
ZeroToOne,
OneToZero,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamType {
Int32,
Int64,
UInt64,
Float64,
Octet,
UInt32Digital,
Int8Array,
Int16Array,
Int32Array,
Int64Array,
UInt64Array,
Float32Array,
Float64Array,
Enum,
GenericPointer,
}
#[derive(Clone)]
pub enum ParamValue {
Int32(i32),
Int64(i64),
UInt64(u64),
Float64(f64),
Octet(String),
UInt32Digital(u32),
Int8Array(Arc<[i8]>),
Int16Array(Arc<[i16]>),
Int32Array(Arc<[i32]>),
Int64Array(Arc<[i64]>),
UInt64Array(Arc<[u64]>),
Float32Array(Arc<[f32]>),
Float64Array(Arc<[f64]>),
Enum {
index: usize,
choices: Arc<[EnumEntry]>,
},
GenericPointer(Arc<dyn Any + Send + Sync>),
Undefined,
}
impl fmt::Debug for ParamValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Int32(v) => write!(f, "Int32({v:?})"),
Self::Int64(v) => write!(f, "Int64({v:?})"),
Self::UInt64(v) => write!(f, "UInt64({v:?})"),
Self::Float64(v) => write!(f, "Float64({v:?})"),
Self::Octet(v) => write!(f, "Octet({v:?})"),
Self::UInt32Digital(v) => write!(f, "UInt32Digital({v:?})"),
Self::Int8Array(v) => write!(f, "Int8Array({v:?})"),
Self::Int16Array(v) => write!(f, "Int16Array({v:?})"),
Self::Int32Array(v) => write!(f, "Int32Array({v:?})"),
Self::Int64Array(v) => write!(f, "Int64Array({v:?})"),
Self::UInt64Array(v) => write!(f, "UInt64Array({v:?})"),
Self::Float32Array(v) => write!(f, "Float32Array({v:?})"),
Self::Float64Array(v) => write!(f, "Float64Array({v:?})"),
Self::Enum { index, choices } => write!(f, "Enum(index={index}, choices={choices:?})"),
Self::GenericPointer(v) => write!(f, "GenericPointer(<{:?}>)", (*v).type_id()),
Self::Undefined => write!(f, "Undefined"),
}
}
}
impl ParamValue {
pub fn type_name(&self) -> &'static str {
match self {
Self::Int32(_) => "Int32",
Self::Int64(_) => "Int64",
Self::UInt64(_) => "UInt64",
Self::Float64(_) => "Float64",
Self::Octet(_) => "Octet",
Self::UInt32Digital(_) => "UInt32Digital",
Self::Int8Array(_) => "Int8Array",
Self::Int16Array(_) => "Int16Array",
Self::Int32Array(_) => "Int32Array",
Self::Int64Array(_) => "Int64Array",
Self::UInt64Array(_) => "UInt64Array",
Self::Float32Array(_) => "Float32Array",
Self::Float64Array(_) => "Float64Array",
Self::Enum { .. } => "Enum",
Self::GenericPointer(_) => "GenericPointer",
Self::Undefined => "Undefined",
}
}
}
#[derive(Debug, Clone)]
struct ParamEntry {
name: String,
param_type: ParamType,
value: ParamValue,
defined: bool,
status: AsynStatus,
alarm_status: u16,
alarm_severity: u16,
value_changed: bool,
timestamp: Option<SystemTime>,
uint32_interrupt_mask: u32,
uint32_rising_mask: u32,
uint32_falling_mask: u32,
}
impl ParamEntry {
fn new(name: String, param_type: ParamType) -> Self {
let value = match param_type {
ParamType::Int32 => ParamValue::Int32(0),
ParamType::Int64 => ParamValue::Int64(0),
ParamType::UInt64 => ParamValue::UInt64(0),
ParamType::Float64 => ParamValue::Float64(0.0),
ParamType::Octet => ParamValue::Octet(String::new()),
ParamType::UInt32Digital => ParamValue::UInt32Digital(0),
ParamType::Int8Array => ParamValue::Int8Array(Arc::from([] as [i8; 0])),
ParamType::Int16Array => ParamValue::Int16Array(Arc::from([] as [i16; 0])),
ParamType::Int32Array => ParamValue::Int32Array(Arc::from([] as [i32; 0])),
ParamType::Int64Array => ParamValue::Int64Array(Arc::from([] as [i64; 0])),
ParamType::UInt64Array => ParamValue::UInt64Array(Arc::from([] as [u64; 0])),
ParamType::Float32Array => ParamValue::Float32Array(Arc::from([] as [f32; 0])),
ParamType::Float64Array => ParamValue::Float64Array(Arc::from([] as [f64; 0])),
ParamType::Enum => ParamValue::Enum {
index: 0,
choices: Arc::from([EnumEntry {
string: String::new(),
value: 0,
severity: 0,
}]),
},
ParamType::GenericPointer => ParamValue::GenericPointer(Arc::new(())),
};
Self {
name,
param_type,
value,
defined: false,
status: AsynStatus::Success,
alarm_status: 0,
alarm_severity: 0,
value_changed: false,
timestamp: None,
uint32_interrupt_mask: 0,
uint32_rising_mask: 0,
uint32_falling_mask: 0,
}
}
}
pub struct ParamList {
max_addr: usize,
multi_device: bool,
params: Vec<Vec<ParamEntry>>,
name_to_index: HashMap<String, usize>,
}
impl ParamList {
pub fn new(max_addr: usize, multi_device: bool) -> Self {
let max_addr = max_addr.max(1);
Self {
max_addr,
multi_device,
params: (0..max_addr).map(|_| Vec::new()).collect(),
name_to_index: HashMap::new(),
}
}
fn validate_addr(&self, addr: i32) -> AsynResult<usize> {
if !self.multi_device {
return Ok(0);
}
if addr < 0 || (addr as usize) >= self.max_addr {
return Err(AsynError::AddressOutOfRange(addr));
}
Ok(addr as usize)
}
fn get_entry(&self, index: usize, addr: i32) -> AsynResult<&ParamEntry> {
let a = self.validate_addr(addr)?;
self.params[a]
.get(index)
.ok_or(AsynError::ParamIndexOutOfRange(index))
}
fn get_entry_mut(&mut self, index: usize, addr: i32) -> AsynResult<&mut ParamEntry> {
let a = self.validate_addr(addr)?;
self.params[a]
.get_mut(index)
.ok_or(AsynError::ParamIndexOutOfRange(index))
}
pub fn create_param(&mut self, name: &str, param_type: ParamType) -> AsynResult<usize> {
if let Some(&idx) = self.name_to_index.get(name) {
return Ok(idx);
}
self.append_param(name, param_type)
}
pub fn create_param_strict(&mut self, name: &str, param_type: ParamType) -> AsynResult<usize> {
if self.name_to_index.contains_key(name) {
return Err(AsynError::ParamAlreadyExists(name.to_string()));
}
self.append_param(name, param_type)
}
fn append_param(&mut self, name: &str, param_type: ParamType) -> AsynResult<usize> {
let index = if self.params[0].is_empty() {
0
} else {
self.params[0].len()
};
for addr_params in &mut self.params {
addr_params.push(ParamEntry::new(name.to_string(), param_type));
}
self.name_to_index.insert(name.to_string(), index);
Ok(index)
}
pub fn find_param(&self, name: &str) -> Option<usize> {
self.name_to_index.get(name).copied()
}
pub fn param_name(&self, index: usize) -> Option<&str> {
self.params[0].get(index).map(|e| e.name.as_str())
}
pub fn param_type(&self, index: usize) -> Option<ParamType> {
self.params[0].get(index).map(|e| e.param_type)
}
pub fn get_value(&self, index: usize, addr: i32) -> AsynResult<&ParamValue> {
Ok(&self.get_entry(index, addr)?.value)
}
pub fn get_int32(&self, index: usize, addr: i32) -> AsynResult<i32> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int32(v) => Ok(*v),
ParamValue::Enum { index: idx, .. } => Ok(*idx as i32),
other => Err(AsynError::TypeMismatch {
expected: "Int32",
actual: other.type_name(),
}),
}
}
pub fn get_int32_strict(&self, index: usize, addr: i32) -> AsynResult<i32> {
let entry = self.get_entry(index, addr)?;
match &entry.value {
ParamValue::Int32(v) => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(*v)
}
ParamValue::Enum { index: idx, .. } => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(*idx as i32)
}
other => Err(AsynError::TypeMismatch {
expected: "Int32",
actual: other.type_name(),
}),
}
}
pub fn set_int32(&mut self, index: usize, addr: i32, value: i32) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
match entry.value {
ParamValue::Int32(ref old) => {
if !entry.defined || *old != value {
entry.value = ParamValue::Int32(value);
entry.value_changed = true;
entry.defined = true;
}
}
ParamValue::Enum {
ref choices,
ref mut index,
} => {
let new_idx = value as usize;
if new_idx >= choices.len() {
return Err(AsynError::Status {
status: AsynStatus::Error,
message: format!(
"enum index {new_idx} out of range (0..{})",
choices.len()
),
});
}
if !entry.defined || *index != new_idx {
*index = new_idx;
entry.value_changed = true;
entry.defined = true;
}
}
_ => {
return Err(AsynError::TypeMismatch {
expected: "Int32",
actual: entry.value.type_name(),
});
}
}
Ok(())
}
pub fn get_float64(&self, index: usize, addr: i32) -> AsynResult<f64> {
match &self.get_entry(index, addr)?.value {
ParamValue::Float64(v) => Ok(*v),
other => Err(AsynError::TypeMismatch {
expected: "Float64",
actual: other.type_name(),
}),
}
}
pub fn get_float64_strict(&self, index: usize, addr: i32) -> AsynResult<f64> {
let entry = self.get_entry(index, addr)?;
match &entry.value {
ParamValue::Float64(v) => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(*v)
}
other => Err(AsynError::TypeMismatch {
expected: "Float64",
actual: other.type_name(),
}),
}
}
pub fn set_float64(&mut self, index: usize, addr: i32, value: f64) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::Float64(ref old) = entry.value {
if !entry.defined || *old != value {
entry.value = ParamValue::Float64(value);
entry.value_changed = true;
entry.defined = true;
}
} else {
return Err(AsynError::TypeMismatch {
expected: "Float64",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn get_int64(&self, index: usize, addr: i32) -> AsynResult<i64> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int64(v) => Ok(*v),
other => Err(AsynError::TypeMismatch {
expected: "Int64",
actual: other.type_name(),
}),
}
}
pub fn get_int64_strict(&self, index: usize, addr: i32) -> AsynResult<i64> {
let entry = self.get_entry(index, addr)?;
match &entry.value {
ParamValue::Int64(v) => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(*v)
}
other => Err(AsynError::TypeMismatch {
expected: "Int64",
actual: other.type_name(),
}),
}
}
pub fn set_int64(&mut self, index: usize, addr: i32, value: i64) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::Int64(ref old) = entry.value {
if !entry.defined || *old != value {
entry.value = ParamValue::Int64(value);
entry.value_changed = true;
entry.defined = true;
}
} else {
return Err(AsynError::TypeMismatch {
expected: "Int64",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn get_string(&self, index: usize, addr: i32) -> AsynResult<&str> {
match &self.get_entry(index, addr)?.value {
ParamValue::Octet(s) => Ok(s),
other => Err(AsynError::TypeMismatch {
expected: "Octet",
actual: other.type_name(),
}),
}
}
pub fn get_string_strict(&self, index: usize, addr: i32) -> AsynResult<&str> {
let entry = self.get_entry(index, addr)?;
match &entry.value {
ParamValue::Octet(s) => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(s)
}
other => Err(AsynError::TypeMismatch {
expected: "Octet",
actual: other.type_name(),
}),
}
}
pub fn set_string(&mut self, index: usize, addr: i32, value: String) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::Octet(ref old) = entry.value {
if !entry.defined || *old != value {
entry.value = ParamValue::Octet(value);
entry.value_changed = true;
entry.defined = true;
}
} else {
return Err(AsynError::TypeMismatch {
expected: "Octet",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn get_uint32(&self, index: usize, addr: i32) -> AsynResult<u32> {
match &self.get_entry(index, addr)?.value {
ParamValue::UInt32Digital(v) => Ok(*v),
other => Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: other.type_name(),
}),
}
}
pub fn get_uint32_strict(&self, index: usize, addr: i32) -> AsynResult<u32> {
let entry = self.get_entry(index, addr)?;
match &entry.value {
ParamValue::UInt32Digital(v) => {
if !entry.defined {
return Err(AsynError::ParamUndefined(index));
}
Ok(*v)
}
other => Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: other.type_name(),
}),
}
}
pub fn set_uint32(&mut self, index: usize, addr: i32, value: u32, mask: u32) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::UInt32Digital(ref old) = entry.value {
let was_defined = entry.defined;
let starting = if was_defined { *old } else { 0 };
let new_val = (starting & !mask) | (value & mask);
let changed_bits = if was_defined {
starting ^ new_val
} else {
new_val
};
if !was_defined || starting != new_val {
entry.uint32_interrupt_mask = changed_bits;
entry.value = ParamValue::UInt32Digital(new_val);
entry.value_changed = true;
entry.defined = true;
}
} else {
return Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn get_uint32_interrupt_mask(&self, index: usize, addr: i32) -> AsynResult<u32> {
Ok(self.get_entry(index, addr)?.uint32_interrupt_mask)
}
pub fn set_uint32_interrupt(
&mut self,
index: usize,
addr: i32,
mask: u32,
reason: InterruptReason,
) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if entry.param_type != ParamType::UInt32Digital {
return Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: entry.value.type_name(),
});
}
match reason {
InterruptReason::ZeroToOne => entry.uint32_rising_mask = mask,
InterruptReason::OneToZero => entry.uint32_falling_mask = mask,
InterruptReason::Both => {
entry.uint32_rising_mask = mask;
entry.uint32_falling_mask = mask;
}
}
Ok(())
}
pub fn clear_uint32_interrupt(&mut self, index: usize, addr: i32, mask: u32) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if entry.param_type != ParamType::UInt32Digital {
return Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: entry.value.type_name(),
});
}
entry.uint32_rising_mask &= !mask;
entry.uint32_falling_mask &= !mask;
Ok(())
}
pub fn get_uint32_interrupt(
&self,
index: usize,
addr: i32,
reason: InterruptReason,
) -> AsynResult<u32> {
let entry = self.get_entry(index, addr)?;
if entry.param_type != ParamType::UInt32Digital {
return Err(AsynError::TypeMismatch {
expected: "UInt32Digital",
actual: entry.value.type_name(),
});
}
Ok(match reason {
InterruptReason::ZeroToOne => entry.uint32_rising_mask,
InterruptReason::OneToZero => entry.uint32_falling_mask,
InterruptReason::Both => entry.uint32_rising_mask | entry.uint32_falling_mask,
})
}
pub fn get_float64_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[f64]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Float64Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Float64Array",
actual: other.type_name(),
}),
}
}
pub fn set_float64_array(&mut self, index: usize, addr: i32, data: Vec<f64>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Float64Array(_)) {
entry.value = ParamValue::Float64Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Float64Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_int32_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[i32]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int32Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Int32Array",
actual: other.type_name(),
}),
}
}
pub fn set_int32_array(&mut self, index: usize, addr: i32, data: Vec<i32>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Int32Array(_)) {
entry.value = ParamValue::Int32Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Int32Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_int8_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[i8]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int8Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Int8Array",
actual: other.type_name(),
}),
}
}
pub fn set_int8_array(&mut self, index: usize, addr: i32, data: Vec<i8>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Int8Array(_)) {
entry.value = ParamValue::Int8Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Int8Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_int16_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[i16]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int16Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Int16Array",
actual: other.type_name(),
}),
}
}
pub fn set_int16_array(&mut self, index: usize, addr: i32, data: Vec<i16>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Int16Array(_)) {
entry.value = ParamValue::Int16Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Int16Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_int64_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[i64]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Int64Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Int64Array",
actual: other.type_name(),
}),
}
}
pub fn set_int64_array(&mut self, index: usize, addr: i32, data: Vec<i64>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Int64Array(_)) {
entry.value = ParamValue::Int64Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Int64Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_float32_array(&self, index: usize, addr: i32) -> AsynResult<Arc<[f32]>> {
match &self.get_entry(index, addr)?.value {
ParamValue::Float32Array(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "Float32Array",
actual: other.type_name(),
}),
}
}
pub fn set_float32_array(&mut self, index: usize, addr: i32, data: Vec<f32>) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::Float32Array(_)) {
entry.value = ParamValue::Float32Array(Arc::from(data));
entry.value_changed = true;
entry.defined = true;
Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "Float32Array",
actual: entry.value.type_name(),
})
}
}
pub fn get_enum(&self, index: usize, addr: i32) -> AsynResult<(usize, Arc<[EnumEntry]>)> {
match &self.get_entry(index, addr)?.value {
ParamValue::Enum {
index: idx,
choices,
} => Ok((*idx, choices.clone())),
other => Err(AsynError::TypeMismatch {
expected: "Enum",
actual: other.type_name(),
}),
}
}
pub fn set_enum_index(&mut self, index: usize, addr: i32, value: usize) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::Enum {
ref choices,
index: ref mut idx,
} = entry.value
{
if value >= choices.len() {
return Err(AsynError::Status {
status: AsynStatus::Error,
message: format!("enum index {value} out of range (0..{})", choices.len()),
});
}
if *idx != value {
*idx = value;
entry.value_changed = true;
entry.defined = true;
}
} else {
return Err(AsynError::TypeMismatch {
expected: "Enum",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn set_enum_choices(
&mut self,
index: usize,
addr: i32,
choices: Arc<[EnumEntry]>,
) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if let ParamValue::Enum {
index: ref mut idx,
choices: ref mut ch,
} = entry.value
{
*ch = choices;
if *idx >= ch.len() {
*idx = 0;
}
entry.value_changed = true;
entry.defined = true;
} else {
return Err(AsynError::TypeMismatch {
expected: "Enum",
actual: entry.value.type_name(),
});
}
Ok(())
}
pub fn get_generic_pointer(
&self,
index: usize,
addr: i32,
) -> AsynResult<Arc<dyn Any + Send + Sync>> {
match &self.get_entry(index, addr)?.value {
ParamValue::GenericPointer(v) => Ok(v.clone()),
other => Err(AsynError::TypeMismatch {
expected: "GenericPointer",
actual: other.type_name(),
}),
}
}
pub fn set_generic_pointer(
&mut self,
index: usize,
addr: i32,
value: Arc<dyn Any + Send + Sync>,
) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
if matches!(entry.value, ParamValue::GenericPointer(_)) {
entry.value = ParamValue::GenericPointer(value);
entry.value_changed = true;
entry.defined = true; Ok(())
} else {
Err(AsynError::TypeMismatch {
expected: "GenericPointer",
actual: entry.value.type_name(),
})
}
}
pub fn is_param_defined(&self, index: usize, addr: i32) -> AsynResult<bool> {
Ok(self.get_entry(index, addr)?.defined)
}
pub fn set_param_status(
&mut self,
index: usize,
addr: i32,
status: AsynStatus,
alarm_status: u16,
alarm_severity: u16,
) -> AsynResult<()> {
let entry = self.get_entry_mut(index, addr)?;
entry.status = status;
entry.alarm_status = alarm_status;
entry.alarm_severity = alarm_severity;
Ok(())
}
pub fn get_param_status(&self, index: usize, addr: i32) -> AsynResult<(AsynStatus, u16, u16)> {
let entry = self.get_entry(index, addr)?;
Ok((entry.status, entry.alarm_status, entry.alarm_severity))
}
pub fn set_timestamp(&mut self, index: usize, addr: i32, ts: SystemTime) -> AsynResult<()> {
self.get_entry_mut(index, addr)?.timestamp = Some(ts);
Ok(())
}
pub fn get_timestamp(&self, index: usize, addr: i32) -> AsynResult<Option<SystemTime>> {
Ok(self.get_entry(index, addr)?.timestamp)
}
pub fn take_changed_single(&mut self, index: usize, addr: i32) -> AsynResult<bool> {
let entry = self.get_entry_mut(index, addr)?;
let was_changed = entry.value_changed;
entry.value_changed = false;
Ok(was_changed)
}
pub fn mark_changed(&mut self, index: usize, addr: i32) -> AsynResult<()> {
self.get_entry_mut(index, addr)?.value_changed = true;
Ok(())
}
pub fn take_changed(&mut self, addr: i32) -> AsynResult<Vec<usize>> {
let a = self.validate_addr(addr)?;
let mut changed = Vec::new();
for (i, entry) in self.params[a].iter_mut().enumerate() {
if entry.value_changed {
entry.value_changed = false;
changed.push(i);
}
}
Ok(changed)
}
pub fn len(&self) -> usize {
self.params[0].len()
}
pub fn is_empty(&self) -> bool {
self.params[0].is_empty()
}
}
#[derive(Default, Debug, Clone)]
pub struct AsynParamSet {
defs: Vec<(String, ParamType)>,
}
impl AsynParamSet {
pub fn new() -> Self {
Self { defs: Vec::new() }
}
pub fn add(&mut self, name: &str, ty: ParamType) -> usize {
let slot = self.defs.len();
self.defs.push((name.to_string(), ty));
slot
}
pub fn create_all(&self, params: &mut ParamList) -> AsynResult<Vec<usize>> {
let mut indices = Vec::with_capacity(self.defs.len());
for (name, ty) in &self.defs {
indices.push(params.create_param(name, *ty)?);
}
Ok(indices)
}
pub fn len(&self) -> usize {
self.defs.len()
}
pub fn is_empty(&self) -> bool {
self.defs.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, ParamType)> {
self.defs.iter().map(|(n, t)| (n.as_str(), *t))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_and_find() {
let mut pl = ParamList::new(1, false);
let i0 = pl.create_param("TEMP", ParamType::Float64).unwrap();
let i1 = pl.create_param("COUNT", ParamType::Int32).unwrap();
assert_eq!(i0, 0);
assert_eq!(i1, 1);
assert_eq!(pl.find_param("TEMP"), Some(0));
assert_eq!(pl.find_param("COUNT"), Some(1));
assert_eq!(pl.find_param("NOPE"), None);
assert_eq!(pl.create_param("TEMP", ParamType::Float64).unwrap(), 0);
}
#[test]
fn test_create_param_strict_duplicate_returns_already_exists() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param_strict("VAL", ParamType::Int32).unwrap();
assert_eq!(idx, 0);
match pl.create_param_strict("VAL", ParamType::Int32) {
Err(AsynError::ParamAlreadyExists(name)) => assert_eq!(name, "VAL"),
other => panic!("expected ParamAlreadyExists, got {other:?}"),
}
match pl.create_param_strict("VAL", ParamType::Int32) {
Err(AsynError::ParamAlreadyExists(_)) => {}
other => panic!("strict must observe lax-created names, got {other:?}"),
}
assert_eq!(pl.create_param("VAL", ParamType::Int32).unwrap(), 0);
}
#[test]
fn test_create_param_strict_distinct_names_succeed() {
let mut pl = ParamList::new(1, false);
let a = pl.create_param_strict("A", ParamType::Int32).unwrap();
let b = pl.create_param_strict("B", ParamType::Float64).unwrap();
assert_eq!(a, 0);
assert_eq!(b, 1);
assert_eq!(pl.find_param("A"), Some(0));
assert_eq!(pl.find_param("B"), Some(1));
}
#[test]
fn test_uint32_set_get_clear_interrupt_masks() {
let mut pl = ParamList::new(1, false);
let idx = pl
.create_param_strict("BITS", ParamType::UInt32Digital)
.unwrap();
pl.set_uint32_interrupt(idx, 0, 0xF0, InterruptReason::ZeroToOne)
.unwrap();
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::ZeroToOne)
.unwrap(),
0xF0
);
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::OneToZero)
.unwrap(),
0x00
);
pl.set_uint32_interrupt(idx, 0, 0x0F, InterruptReason::OneToZero)
.unwrap();
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::Both)
.unwrap(),
0xFF
);
pl.clear_uint32_interrupt(idx, 0, 0x10).unwrap();
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::ZeroToOne)
.unwrap(),
0xE0
);
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::OneToZero)
.unwrap(),
0x0F
);
pl.set_uint32_interrupt(idx, 0, 0xAA, InterruptReason::Both)
.unwrap();
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::ZeroToOne)
.unwrap(),
0xAA
);
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::OneToZero)
.unwrap(),
0xAA
);
assert_eq!(
pl.get_uint32_interrupt(idx, 0, InterruptReason::Both)
.unwrap(),
0xAA
);
}
#[test]
fn test_uint32_interrupt_type_mismatch_rejects_non_uint32() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
match pl.set_uint32_interrupt(idx, 0, 0xFF, InterruptReason::Both) {
Err(AsynError::TypeMismatch { expected, .. }) => {
assert_eq!(expected, "UInt32Digital")
}
other => panic!("expected TypeMismatch, got {other:?}"),
}
match pl.clear_uint32_interrupt(idx, 0, 0xFF) {
Err(AsynError::TypeMismatch { .. }) => {}
other => panic!("expected TypeMismatch, got {other:?}"),
}
match pl.get_uint32_interrupt(idx, 0, InterruptReason::Both) {
Err(AsynError::TypeMismatch { .. }) => {}
other => panic!("expected TypeMismatch, got {other:?}"),
}
}
#[test]
fn test_get_set_int32() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
assert_eq!(pl.get_int32(idx, 0).unwrap(), 0);
pl.set_int32(idx, 0, 42).unwrap();
assert_eq!(pl.get_int32(idx, 0).unwrap(), 42);
}
#[test]
fn test_get_set_float64() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("TEMP", ParamType::Float64).unwrap();
pl.set_float64(idx, 0, 3.14).unwrap();
assert!((pl.get_float64(idx, 0).unwrap() - 3.14).abs() < 1e-10);
}
#[test]
fn test_get_set_string() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MSG", ParamType::Octet).unwrap();
pl.set_string(idx, 0, "hello".into()).unwrap();
assert_eq!(pl.get_string(idx, 0).unwrap(), "hello");
}
#[test]
fn test_get_set_uint32_mask() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("BITS", ParamType::UInt32Digital).unwrap();
pl.set_uint32(idx, 0, 0xFF, 0x0F).unwrap();
assert_eq!(pl.get_uint32(idx, 0).unwrap(), 0x0F);
pl.set_uint32(idx, 0, 0xFF, 0xF0).unwrap();
assert_eq!(pl.get_uint32(idx, 0).unwrap(), 0xFF);
}
#[test]
fn test_multi_addr_isolation() {
let mut pl = ParamList::new(3, true);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
pl.set_int32(idx, 0, 10).unwrap();
pl.set_int32(idx, 1, 20).unwrap();
pl.set_int32(idx, 2, 30).unwrap();
assert_eq!(pl.get_int32(idx, 0).unwrap(), 10);
assert_eq!(pl.get_int32(idx, 1).unwrap(), 20);
assert_eq!(pl.get_int32(idx, 2).unwrap(), 30);
}
#[test]
fn test_addr_out_of_range() {
let pl = ParamList::new(2, true);
assert!(pl.validate_addr(-1).is_err());
assert!(pl.validate_addr(2).is_err());
assert!(pl.validate_addr(0).is_ok());
assert!(pl.validate_addr(1).is_ok());
}
#[test]
fn test_addr_normalize_single_device() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int32).unwrap();
pl.set_int32(idx, 0, 99).unwrap();
assert_eq!(pl.get_int32(idx, 5).unwrap(), 99);
assert_eq!(pl.get_int32(idx, -1).unwrap(), 99);
}
#[test]
fn test_index_out_of_range() {
let pl = ParamList::new(1, false);
assert!(pl.get_int32(999, 0).is_err());
}
#[test]
fn test_type_mismatch() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
assert!(pl.get_float64(idx, 0).is_err());
assert!(pl.set_float64(idx, 0, 1.0).is_err());
}
#[test]
fn test_change_tracking() {
let mut pl = ParamList::new(1, false);
let i0 = pl.create_param("A", ParamType::Int32).unwrap();
let i1 = pl.create_param("B", ParamType::Float64).unwrap();
pl.set_int32(i0, 0, 1).unwrap();
pl.set_float64(i1, 0, 2.0).unwrap();
let changed = pl.take_changed(0).unwrap();
assert_eq!(changed.len(), 2);
assert_eq!(changed[0], 0);
assert_eq!(changed[1], 1);
let changed2 = pl.take_changed(0).unwrap();
assert!(changed2.is_empty());
}
#[test]
fn test_same_value_no_change() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int32).unwrap();
pl.set_int32(idx, 0, 42).unwrap();
let _ = pl.take_changed(0).unwrap();
pl.set_int32(idx, 0, 42).unwrap();
let changed = pl.take_changed(0).unwrap();
assert!(changed.is_empty());
}
#[test]
fn test_array_params() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("WF", ParamType::Float64Array).unwrap();
pl.set_float64_array(idx, 0, vec![1.0, 2.0, 3.0]).unwrap();
let arr = pl.get_float64_array(idx, 0).unwrap();
assert_eq!(&*arr, &[1.0, 2.0, 3.0]);
}
#[test]
fn test_param_status() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int32).unwrap();
pl.set_param_status(idx, 0, AsynStatus::Timeout, 1, 2)
.unwrap();
let (st, as_, sev) = pl.get_param_status(idx, 0).unwrap();
assert_eq!(st, AsynStatus::Timeout);
assert_eq!(as_, 1);
assert_eq!(sev, 2);
}
#[test]
fn test_param_name_and_type() {
let mut pl = ParamList::new(1, false);
pl.create_param("TEMP", ParamType::Float64).unwrap();
assert_eq!(pl.param_name(0), Some("TEMP"));
assert_eq!(pl.param_type(0), Some(ParamType::Float64));
assert_eq!(pl.param_name(99), None);
}
#[test]
fn test_timestamp_none_by_default() {
let mut pl = ParamList::new(1, false);
pl.create_param("V", ParamType::Int32).unwrap();
assert_eq!(pl.get_timestamp(0, 0).unwrap(), None);
}
#[test]
fn test_timestamp_set_get() {
let mut pl = ParamList::new(1, false);
pl.create_param("V", ParamType::Int32).unwrap();
let ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(12345);
pl.set_timestamp(0, 0, ts).unwrap();
assert_eq!(pl.get_timestamp(0, 0).unwrap(), Some(ts));
}
#[test]
fn test_take_changed_returns_indices() {
let mut pl = ParamList::new(1, false);
pl.create_param("A", ParamType::Int32).unwrap();
pl.create_param("B", ParamType::Float64).unwrap();
pl.create_param("C", ParamType::Octet).unwrap();
pl.set_int32(0, 0, 1).unwrap();
pl.set_string(2, 0, "x".into()).unwrap();
let changed = pl.take_changed(0).unwrap();
assert_eq!(changed, vec![0, 2]);
}
#[test]
fn test_enum_default_sentinel() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MODE", ParamType::Enum).unwrap();
let (index, choices) = pl.get_enum(idx, 0).unwrap();
assert_eq!(index, 0);
assert_eq!(choices.len(), 1);
assert_eq!(choices[0].string, "");
assert_eq!(choices[0].value, 0);
assert_eq!(choices[0].severity, 0);
}
#[test]
fn test_enum_set_get_index() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MODE", ParamType::Enum).unwrap();
let choices: Arc<[EnumEntry]> = Arc::from(vec![
EnumEntry {
string: "Off".into(),
value: 0,
severity: 0,
},
EnumEntry {
string: "On".into(),
value: 1,
severity: 0,
},
]);
pl.set_enum_choices(idx, 0, choices).unwrap();
pl.set_enum_index(idx, 0, 1).unwrap();
let (index, _) = pl.get_enum(idx, 0).unwrap();
assert_eq!(index, 1);
}
#[test]
fn test_enum_index_out_of_range() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MODE", ParamType::Enum).unwrap();
assert!(pl.set_enum_index(idx, 0, 1).is_err());
}
#[test]
fn test_enum_type_mismatch() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
assert!(pl.get_enum(idx, 0).is_err());
}
#[test]
fn test_enum_choices_update_resets_index() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MODE", ParamType::Enum).unwrap();
let choices: Arc<[EnumEntry]> = Arc::from(vec![
EnumEntry {
string: "A".into(),
value: 0,
severity: 0,
},
EnumEntry {
string: "B".into(),
value: 1,
severity: 0,
},
EnumEntry {
string: "C".into(),
value: 2,
severity: 0,
},
]);
pl.set_enum_choices(idx, 0, choices).unwrap();
pl.set_enum_index(idx, 0, 2).unwrap();
let new_choices: Arc<[EnumEntry]> = Arc::from(vec![EnumEntry {
string: "X".into(),
value: 0,
severity: 0,
}]);
pl.set_enum_choices(idx, 0, new_choices).unwrap();
let (index, choices) = pl.get_enum(idx, 0).unwrap();
assert_eq!(index, 0);
assert_eq!(choices.len(), 1);
}
#[test]
fn test_enum_set_choices_marks_changed() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("MODE", ParamType::Enum).unwrap();
let _ = pl.take_changed(0).unwrap(); let choices: Arc<[EnumEntry]> = Arc::from(vec![EnumEntry {
string: "A".into(),
value: 0,
severity: 0,
}]);
pl.set_enum_choices(idx, 0, choices).unwrap();
let changed = pl.take_changed(0).unwrap();
assert!(changed.contains(&idx));
}
#[test]
fn test_generic_pointer_default() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("PTR", ParamType::GenericPointer).unwrap();
let val = pl.get_generic_pointer(idx, 0).unwrap();
assert!(val.downcast_ref::<()>().is_some());
}
#[test]
fn test_generic_pointer_set_get_downcast() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("PTR", ParamType::GenericPointer).unwrap();
let data: Arc<dyn Any + Send + Sync> = Arc::new(42i32);
pl.set_generic_pointer(idx, 0, data).unwrap();
let val = pl.get_generic_pointer(idx, 0).unwrap();
assert_eq!(*val.downcast_ref::<i32>().unwrap(), 42);
}
#[test]
fn test_generic_pointer_downcast_wrong_type() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("PTR", ParamType::GenericPointer).unwrap();
let data: Arc<dyn Any + Send + Sync> = Arc::new(42i32);
pl.set_generic_pointer(idx, 0, data).unwrap();
let val = pl.get_generic_pointer(idx, 0).unwrap();
assert!(val.downcast_ref::<String>().is_none());
}
#[test]
fn test_generic_pointer_type_mismatch() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("VAL", ParamType::Int32).unwrap();
assert!(pl.get_generic_pointer(idx, 0).is_err());
}
#[test]
fn test_generic_pointer_debug_shows_type_id() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("PTR", ParamType::GenericPointer).unwrap();
let data: Arc<dyn Any + Send + Sync> = Arc::new(vec![1, 2, 3]);
pl.set_generic_pointer(idx, 0, data).unwrap();
let val = pl.get_value(idx, 0).unwrap();
let s = format!("{val:?}");
assert!(s.contains("GenericPointer"));
assert!(s.contains("TypeId"));
}
#[test]
fn test_get_set_int64() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("BIG", ParamType::Int64).unwrap();
assert_eq!(pl.get_int64(idx, 0).unwrap(), 0);
pl.set_int64(idx, 0, i64::MAX).unwrap();
assert_eq!(pl.get_int64(idx, 0).unwrap(), i64::MAX);
}
#[test]
fn test_int64_type_mismatch() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int32).unwrap();
assert!(pl.get_int64(idx, 0).is_err());
assert!(pl.set_int64(idx, 0, 1).is_err());
}
#[test]
fn test_int64_same_value_no_change() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int64).unwrap();
pl.set_int64(idx, 0, 42).unwrap();
let _ = pl.take_changed(0).unwrap();
pl.set_int64(idx, 0, 42).unwrap();
let changed = pl.take_changed(0).unwrap();
assert!(changed.is_empty());
}
#[test]
fn test_int64_change_tracking() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int64).unwrap();
pl.set_int64(idx, 0, 100).unwrap();
let changed = pl.take_changed(0).unwrap();
assert_eq!(changed, vec![idx]);
}
#[test]
fn asyn_param_set_add_returns_slot_index_in_order() {
let mut set = AsynParamSet::new();
assert_eq!(set.add("Temperature", ParamType::Float64), 0);
assert_eq!(set.add("Status", ParamType::Int32), 1);
assert_eq!(set.add("Tag", ParamType::Octet), 2);
assert_eq!(set.len(), 3);
}
#[test]
fn asyn_param_set_create_all_assigns_indices_in_order() {
let mut set = AsynParamSet::new();
let temp_slot = set.add("Temperature", ParamType::Float64);
let status_slot = set.add("Status", ParamType::Int32);
let tag_slot = set.add("Tag", ParamType::Octet);
let mut pl = ParamList::new(1, false);
let indices = set.create_all(&mut pl).unwrap();
assert_eq!(indices.len(), 3);
assert_eq!(indices[temp_slot], 0);
assert_eq!(indices[status_slot], 1);
assert_eq!(indices[tag_slot], 2);
assert_eq!(pl.find_param("Temperature"), Some(0));
assert_eq!(pl.find_param("Status"), Some(1));
assert_eq!(pl.find_param("Tag"), Some(2));
}
#[test]
fn asyn_param_set_iter_preserves_registration_order() {
let mut set = AsynParamSet::new();
set.add("A", ParamType::Int32);
set.add("B", ParamType::Float64);
set.add("C", ParamType::Octet);
let names: Vec<&str> = set.iter().map(|(n, _)| n).collect();
assert_eq!(names, vec!["A", "B", "C"]);
}
#[test]
fn asyn_param_set_empty_create_all_is_noop() {
let set = AsynParamSet::new();
let mut pl = ParamList::new(1, false);
let idx = set.create_all(&mut pl).unwrap();
assert!(idx.is_empty());
assert!(pl.is_empty());
}
#[test]
fn asyn_param_set_duplicate_name_returns_existing_index() {
let mut set = AsynParamSet::new();
set.add("X", ParamType::Int32);
set.add("X", ParamType::Int32);
let mut pl = ParamList::new(1, false);
let idx = set.create_all(&mut pl).unwrap();
assert_eq!(idx, vec![0, 0]);
}
#[test]
fn get_int32_strict_undefined_returns_param_undefined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("U", ParamType::Int32).unwrap();
assert_eq!(pl.get_int32(idx, 0).unwrap(), 0);
let err = pl.get_int32_strict(idx, 0).unwrap_err();
assert!(matches!(err, AsynError::ParamUndefined(i) if i == idx));
pl.set_int32(idx, 0, 7).unwrap();
assert_eq!(pl.get_int32_strict(idx, 0).unwrap(), 7);
}
#[test]
fn get_float64_strict_undefined_returns_param_undefined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("U", ParamType::Float64).unwrap();
assert_eq!(pl.get_float64(idx, 0).unwrap(), 0.0);
assert!(matches!(
pl.get_float64_strict(idx, 0).unwrap_err(),
AsynError::ParamUndefined(i) if i == idx
));
}
#[test]
fn get_int64_strict_undefined_returns_param_undefined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("U", ParamType::Int64).unwrap();
assert!(matches!(
pl.get_int64_strict(idx, 0).unwrap_err(),
AsynError::ParamUndefined(i) if i == idx
));
}
#[test]
fn get_uint32_strict_undefined_returns_param_undefined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("U", ParamType::UInt32Digital).unwrap();
assert!(matches!(
pl.get_uint32_strict(idx, 0).unwrap_err(),
AsynError::ParamUndefined(i) if i == idx
));
}
#[test]
fn get_string_strict_undefined_returns_param_undefined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("U", ParamType::Octet).unwrap();
assert!(matches!(
pl.get_string_strict(idx, 0).unwrap_err(),
AsynError::ParamUndefined(i) if i == idx
));
}
#[test]
fn get_strict_checks_type_before_undefined_c_parity() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("F", ParamType::Float64).unwrap();
assert!(matches!(
pl.get_int32_strict(idx, 0).unwrap_err(),
AsynError::TypeMismatch {
expected: "Int32",
..
}
));
}
#[test]
fn set_int32_first_write_with_default_value_flips_defined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Int32).unwrap();
assert!(!pl.is_param_defined(idx, 0).unwrap());
pl.set_int32(idx, 0, 0).unwrap();
assert!(pl.is_param_defined(idx, 0).unwrap());
assert!(pl.take_changed_single(idx, 0).unwrap());
assert_eq!(pl.get_int32_strict(idx, 0).unwrap(), 0);
}
#[test]
fn set_float64_first_write_with_default_value_flips_defined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Float64).unwrap();
pl.set_float64(idx, 0, 0.0).unwrap();
assert!(pl.is_param_defined(idx, 0).unwrap());
assert_eq!(pl.get_float64_strict(idx, 0).unwrap(), 0.0);
}
#[test]
fn set_uint32_first_write_zero_mask_zero_flips_defined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::UInt32Digital).unwrap();
pl.set_uint32(idx, 0, 0, 0xFFFF).unwrap();
assert!(pl.is_param_defined(idx, 0).unwrap());
assert!(pl.take_changed_single(idx, 0).unwrap());
assert_eq!(pl.get_uint32_strict(idx, 0).unwrap(), 0);
}
#[test]
fn set_string_first_write_empty_flips_defined() {
let mut pl = ParamList::new(1, false);
let idx = pl.create_param("V", ParamType::Octet).unwrap();
pl.set_string(idx, 0, String::new()).unwrap();
assert!(pl.is_param_defined(idx, 0).unwrap());
assert_eq!(pl.get_string_strict(idx, 0).unwrap(), "");
}
}