use std::time::Duration;
use std::borrow::Cow;
use std::fmt::Debug;
use crate::value::AttrDataFormat;
use crate::error::{Error, ErrorKind, Result};
use crate::dev::{
AttrArchiveEventConfig, AttrChangeEventConfig, AttrConfig, AttrDeltaConfig, AttrInfo,
AttrMemorized, AttrMode, AttrNumber, AttrPeriodicEventConfig, AttrRangeConfig, CommandInfo,
DevInfo, DisplayLevel,
};
use super::super::orb::cdr::{CdrReader, CdrWriter};
use super::super::DevVersion;
use super::{
attr_data_kind, attr_data_type, command_data_kind, command_data_type, read_attr_data_format,
write_attr_data_format, DEV_LONG, DEV_LONG64, DEV_SHORT, DEV_UCHAR, DEV_ULONG, DEV_ULONG64,
DEV_USHORT, DL_EXPERT, DL_OPERATOR, DL_UNKNOWN, NOT_A_NUMBER, NOT_SPECIFIED, NO_DESCRIPTION,
NO_DISPLAY_UNIT, NO_LABEL, NO_STANDARD_UNIT, UNINITIALIZED, WT_READ, WT_READ_WITH_WRITE,
WT_READ_WRITE, WT_UNKNOWN, WT_WRITE,
};
fn read_display_level(buf: &mut CdrReader<'_>) -> Result<DisplayLevel> {
Ok(match buf.read_ulong()? {
DL_OPERATOR | DL_UNKNOWN => DisplayLevel::Operator,
DL_EXPERT => DisplayLevel::Expert,
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Display level value {value} is invalid"))),
})
}
fn read_prop<'a>(buf: &mut CdrReader<'a>, unset: &str) -> Result<Option<Cow<'static, str>>> {
let value = buf.read_string()?;
Ok(if value.is_empty() || value == unset {
None
} else {
Some(Cow::Owned(value.into_owned()))
})
}
fn read_value_str<'a>(buf: &mut CdrReader<'a>) -> Result<Option<Cow<'a, str>>> {
let value = buf.read_string()?;
Ok(if value.is_empty() || value == NOT_SPECIFIED || value == NOT_A_NUMBER {
None
} else {
Some(value)
})
}
fn read_value_prop<'a>(buf: &mut CdrReader<'a>) -> Result<Option<f64>> {
Ok(read_value_str(buf)?
.and_then(|value| value.trim().parse::<f64>().ok())
.filter(|value| value.is_finite()))
}
fn read_limit_prop<'a>(buf: &mut CdrReader<'a>, kind: LimitKind) -> Result<Option<AttrNumber>> {
let Some(value) = read_value_str(buf)? else {
return Ok(None);
};
let value = value.trim();
let typed = match kind {
LimitKind::Long => value.parse::<i64>().ok().map(AttrNumber::Long),
LimitKind::ULong => value.parse::<u64>().ok().map(AttrNumber::ULong),
LimitKind::Double => None,
};
Ok(typed.or_else(|| value.parse::<f64>().ok()
.filter(|value| value.is_finite())
.map(AttrNumber::Double)))
}
fn limit_kind(data_type: i32) -> LimitKind {
match data_type {
DEV_SHORT | DEV_LONG | DEV_LONG64 => LimitKind::Long,
DEV_UCHAR | DEV_USHORT | DEV_ULONG | DEV_ULONG64 => LimitKind::ULong,
_ => LimitKind::Double,
}
}
#[derive(Debug, Clone, Copy)]
enum LimitKind {
Double,
Long,
ULong,
}
fn read_duration_prop<'a>(buf: &mut CdrReader<'a>, unit: Duration) -> Result<Option<Duration>> {
let Some(value) = read_value_str(buf)? else {
return Ok(None);
};
Ok(value.trim().parse::<u32>().ok().map(|value| unit * value))
}
pub fn read_command_info<'a>(buf: &mut CdrReader<'a>, version: DevVersion) -> Result<(Cow<'a, str>, Result<CommandInfo>)> {
let name = buf.read_string()?;
let display_level = if version >= DevVersion::V2 {
read_display_level(buf)?
} else {
DisplayLevel::Operator
};
let _cmd_tag = buf.read_long()?;
let in_type = buf.read_long()?;
let out_type = buf.read_long()?;
let in_doc = read_prop(buf, UNINITIALIZED)?;
let out_doc = read_prop(buf, UNINITIALIZED)?;
let info = match (command_data_kind(in_type), command_data_kind(out_type)) {
(Ok(in_kind), Ok(out_kind)) => Ok(CommandInfo {
display_level,
in_kind,
out_kind,
in_doc,
out_doc,
}),
(Err(err), _) |
(_, Err(err)) => Err(err),
};
Ok((name, info))
}
pub fn read_attr_info<'a>(buf: &mut CdrReader<'a>, version: DevVersion) -> Result<(Cow<'a, str>, Result<AttrInfo>)> {
let name = buf.read_string()?;
let writable = buf.read_ulong()?;
let format = read_attr_data_format(buf)?;
let data_type = buf.read_long()?;
let limits = limit_kind(data_type);
let mut memorized = None;
if version >= DevVersion::V5 {
memorized = match (buf.read_bool()?, buf.read_bool()?) {
(false, _) => None,
(true, false) => Some(AttrMemorized::RestoreSetPoint),
(true, true) => Some(AttrMemorized::RestoreAndWrite),
};
}
let max_dim_x = buf.read_long()?;
let max_dim_y = buf.read_long()?;
let max_dim = |value: i32| usize::try_from(value).unwrap_or(0);
let data_format = match format {
Some(AttrDataFormat::Scalar) => AttrDataFormat::Scalar,
Some(AttrDataFormat::Spectrum(_)) => AttrDataFormat::Spectrum(max_dim(max_dim_x)),
Some(AttrDataFormat::Image(_, _)) => AttrDataFormat::Image(max_dim(max_dim_x), max_dim(max_dim_y)),
None => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Attribute {name:?} has an unknown data format"))),
};
let doc = read_prop(buf, NO_DESCRIPTION)?;
let label = read_prop(buf, NO_LABEL)?;
let unit = read_prop(buf, "")?;
let standard_unit = read_prop(buf, NO_STANDARD_UNIT)?;
let display_unit = read_prop(buf, NO_DISPLAY_UNIT)?;
let display_format = read_prop(buf, NOT_SPECIFIED)?;
let range = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
let mut alarm = AttrRangeConfig::default();
if version <= DevVersion::V2 {
alarm = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
}
let write_attr_name = buf.read_string()?;
let mut display_level = DisplayLevel::Operator;
if version >= DevVersion::V2 {
display_level = read_display_level(buf)?;
}
let mut enum_labels = Vec::new();
if version >= DevVersion::V5 {
let _root_attr_name = buf.read_string()?;
enum_labels = buf.read::<Vec<String>>()?;
}
let mut warning = AttrRangeConfig::default();
let mut delta = None;
let mut change_event = AttrChangeEventConfig::default();
let mut periodic_event = AttrPeriodicEventConfig::default();
let mut archive_event = AttrArchiveEventConfig::default();
if version >= DevVersion::V3 {
alarm = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
warning = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
let delta_time = read_duration_prop(buf, Duration::from_millis(1))?;
let delta_value = read_limit_prop(buf, limits)?;
let _alarm_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
delta = delta_value.map(|value| AttrDeltaConfig {
value,
time: delta_time,
});
change_event = AttrChangeEventConfig {
rel_change: read_value_prop(buf)?,
abs_change: read_value_prop(buf)?,
};
let _change_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
periodic_event = AttrPeriodicEventConfig {
period: read_duration_prop(buf, Duration::from_millis(1))?,
};
let _periodic_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
archive_event = AttrArchiveEventConfig {
rel_change: read_value_prop(buf)?,
abs_change: read_value_prop(buf)?,
period: read_duration_prop(buf, Duration::from_millis(1))?,
};
let _archive_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
}
let extensions = buf.read::<Vec<String>>()?.into_iter()
.map(Cow::Owned)
.collect();
if version >= DevVersion::V3 {
let _sys_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
}
let mode = match writable {
WT_READ => AttrMode::Read,
WT_READ_WITH_WRITE => AttrMode::ReadWithWrite {
write_attr_name: Cow::Owned(write_attr_name.into_owned()),
},
WT_WRITE => AttrMode::Write,
WT_READ_WRITE => AttrMode::ReadWrite,
WT_UNKNOWN => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Attribute {name:?} has an unknown writability"))),
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Attribute writability value {value} is invalid"))),
};
let info = attr_data_kind(data_type).map(|data_kind| AttrInfo {
config: AttrConfig {
doc,
label,
unit,
standard_unit: standard_unit.and_then(|value| value.trim().parse::<f64>().ok()),
display_unit,
display_format,
range,
alarm,
warning,
delta,
change_event,
periodic_event,
archive_event,
enum_labels: enum_labels.into_iter().map(Cow::Owned).collect(),
extensions,
},
mode,
data_kind,
data_format,
display_level,
memorized,
});
Ok((name, info))
}
pub fn read_dev_info(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<DevInfo> {
let class = buf.read_string()?.into_owned();
let server_id = buf.read_string()?.into_owned();
let server_host = buf.read_string()?.into_owned();
let server_version = buf.read_long()?;
let doc_url = read_prop(buf, NOT_SPECIFIED)?;
let mut dev_type = None;
if version >= DevVersion::V3 {
dev_type = read_prop(buf, NOT_SPECIFIED)?;
}
let mut version_info = Vec::new();
if version >= DevVersion::V6 {
let len = buf.read_sequence_len()?;
version_info.try_reserve(len)
.map_err(|_| Error::new(ErrorKind::Internal)
.with_description(format!("Failed to reserve room for the {len} versions of a device info")))?;
for _ in 0..len {
let key = buf.read_string()?.into_owned();
let value = buf.read_string()?.into_owned();
version_info.push((Cow::Owned(key), Cow::Owned(value)));
}
}
Ok(DevInfo {
class,
server_id,
server_host,
server_version,
doc_url,
dev_type,
version_info,
})
}
pub fn write_dev_info(buf: &mut CdrWriter, version: DevVersion, info: &DevInfo) -> Result<()> {
buf.write_string(&info.class)?;
buf.write_string(&info.server_id)?;
buf.write_string(&info.server_host)?;
buf.write_long(info.server_version)?;
write_prop(buf, info.doc_url.as_deref(), NOT_SPECIFIED)?;
if version >= DevVersion::V3 {
write_prop(buf, info.dev_type.as_deref(), NOT_SPECIFIED)?;
}
if version >= DevVersion::V6 {
buf.write_sequence_len(info.version_info.len())?;
for (key, value) in &info.version_info {
buf.write_string(key)?;
buf.write_string(value)?;
}
}
Ok(())
}
pub fn write_command_info(buf: &mut CdrWriter, version: DevVersion, name: &str, info: &CommandInfo) -> Result<()> {
buf.write_string(name)?;
if version >= DevVersion::V2 {
write_display_level(buf, info.display_level)?;
}
buf.write_long(0)?;
buf.write_long(command_data_type(&info.in_kind))?;
buf.write_long(command_data_type(&info.out_kind))?;
write_prop(buf, info.in_doc.as_deref(), UNINITIALIZED)?;
write_prop(buf, info.out_doc.as_deref(), UNINITIALIZED)?;
Ok(())
}
pub fn write_attr_info(buf: &mut CdrWriter, version: DevVersion, name: &str, info: &AttrInfo) -> Result<()> {
let AttrInfo {
config,
mode,
data_kind,
data_format,
display_level,
memorized,
} = info;
buf.write_string(name)?;
buf.write_ulong(match mode {
AttrMode::Read => WT_READ,
AttrMode::ReadWithWrite { .. } => WT_READ_WITH_WRITE,
AttrMode::Write => WT_WRITE,
AttrMode::ReadWrite => WT_READ_WRITE,
})?;
write_attr_data_format(buf, Some(*data_format))?;
let data_type = attr_data_type(data_kind);
buf.write_long(data_type)?;
let limits = limit_kind(data_type);
if version >= DevVersion::V5 {
buf.write_bool(memorized.is_some())?;
buf.write_bool(*memorized == Some(AttrMemorized::RestoreAndWrite))?;
}
let (max_dim_x, max_dim_y) = match data_format {
AttrDataFormat::Scalar => (1, 0),
AttrDataFormat::Spectrum(max_x) => (*max_x, 0),
AttrDataFormat::Image(max_x, max_y) => (*max_x, *max_y),
};
let (Ok(max_dim_x), Ok(max_dim_y)) = (i32::try_from(max_dim_x), i32::try_from(max_dim_y)) else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Maximum dimensions of attribute {name:?} do not fit into a long"))
.with_append_description(format_args!("Got: {max_dim_x}x{max_dim_y}")));
};
buf.write_long(max_dim_x)?;
buf.write_long(max_dim_y)?;
write_prop(buf, config.doc.as_deref(), NO_DESCRIPTION)?;
write_prop(buf, config.label.as_deref(), name)?;
write_prop(buf, config.unit.as_deref(), "")?;
match config.standard_unit {
Some(factor) => buf.write_string(&factor.to_string())?,
None => buf.write_string(NO_STANDARD_UNIT)?,
}
write_prop(buf, config.display_unit.as_deref(), NO_DISPLAY_UNIT)?;
write_prop(buf, config.display_format.as_deref(), NOT_SPECIFIED)?;
write_limit_prop(buf, config.range.min, limits)?;
write_limit_prop(buf, config.range.max, limits)?;
if version <= DevVersion::V2 {
write_limit_prop(buf, config.alarm.min, limits)?;
write_limit_prop(buf, config.alarm.max, limits)?;
}
buf.write_string(match mode {
AttrMode::ReadWithWrite { write_attr_name } => write_attr_name,
_ => "",
})?;
if version >= DevVersion::V2 {
write_display_level(buf, *display_level)?;
}
if version >= DevVersion::V5 {
buf.write_string("")?;
buf.write(&config.enum_labels)?;
}
if version >= DevVersion::V3 {
write_limit_prop(buf, config.alarm.min, limits)?;
write_limit_prop(buf, config.alarm.max, limits)?;
write_limit_prop(buf, config.warning.min, limits)?;
write_limit_prop(buf, config.warning.max, limits)?;
write_duration_prop(buf, config.delta.and_then(|delta| delta.time), Duration::from_millis(1))?;
write_limit_prop(buf, config.delta.map(|delta| delta.value), limits)?;
buf.write_sequence_len(0)?;
write_value_prop(buf, config.change_event.rel_change)?;
write_value_prop(buf, config.change_event.abs_change)?;
buf.write_sequence_len(0)?;
write_duration_prop(buf, config.periodic_event.period, Duration::from_millis(1))?;
buf.write_sequence_len(0)?;
write_value_prop(buf, config.archive_event.rel_change)?;
write_value_prop(buf, config.archive_event.abs_change)?;
write_duration_prop(buf, config.archive_event.period, Duration::from_millis(1))?;
buf.write_sequence_len(0)?;
}
buf.write(&config.extensions)?;
if version >= DevVersion::V3 {
buf.write_sequence_len(0)?;
}
Ok(())
}
fn write_display_level(buf: &mut CdrWriter, level: DisplayLevel) -> Result<()> {
buf.write_ulong(match level {
DisplayLevel::Operator => DL_OPERATOR,
DisplayLevel::Expert => DL_EXPERT,
})
}
fn write_prop(buf: &mut CdrWriter, value: Option<&str>, unset: &str) -> Result<()> {
buf.write_string(value.unwrap_or(unset))
}
fn write_value_prop(buf: &mut CdrWriter, value: Option<f64>) -> Result<()> {
match value {
Some(value) => buf.write_string(&value.to_string()),
None => buf.write_string(NOT_SPECIFIED),
}
}
fn write_limit_prop(buf: &mut CdrWriter, value: Option<AttrNumber>, _kind: LimitKind) -> Result<()> {
match value {
Some(value) => buf.write_string(&value.to_string()),
None => buf.write_string(NOT_SPECIFIED),
}
}
fn write_duration_prop(buf: &mut CdrWriter, value: Option<Duration>, unit: Duration) -> Result<()> {
let Some(value) = value else {
return buf.write_string(NOT_SPECIFIED);
};
let unit = unit.as_nanos().max(1);
let count = (value.as_nanos() + unit / 2) / unit;
buf.write_string(&count.max(1).to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::AttrDataKind;
#[test]
fn attr_info_round_trip() {
let config = AttrConfig {
unit: Some("mm".into()),
range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: None },
alarm: AttrRangeConfig { min: None, max: Some(AttrNumber::Long(10)) },
delta: Some(AttrDeltaConfig { value: AttrNumber::Long(1), time: Some(Duration::from_millis(250)) }),
change_event: AttrChangeEventConfig { rel_change: Some(0.5), abs_change: None },
periodic_event: AttrPeriodicEventConfig { period: Some(Duration::from_millis(1000)) },
..AttrConfig::default()
};
let info = AttrInfo {
config,
mode: AttrMode::ReadWrite,
data_kind: AttrDataKind::Long,
data_format: AttrDataFormat::Scalar,
display_level: DisplayLevel::Operator,
memorized: None,
};
let mut writer = CdrWriter::new();
write_attr_info(&mut writer, DevVersion::V5, "Position", &info).unwrap();
let buf = writer.into_buf().freeze();
let mut expected = info;
expected.config.label = Some("Position".into());
let (name, back) = read_attr_info(&mut CdrReader::new(&buf), DevVersion::V5).unwrap();
assert_eq!(name, "Position");
assert_eq!(back.unwrap(), expected);
}
}