use std::time::{Duration, SystemTime};
use std::ops::{Bound, RangeBounds};
use std::fmt::{self, Debug};
use std::future::Future;
use std::borrow::Cow;
use std::sync::Arc;
use futures::future::BoxFuture;
use crate::dev::{AttrConfig, AttrDeltaConfig, AttrMemorized, AttrNumber, AttrQuality, AttrRangeConfig, AttrRead, DevState, DisplayLevel};
use crate::value::{AttrData, AttrDataFormat, AttrDataKind, CommandData, CommandDataKind, FromValue, IntoValue, Scalar, Value};
use crate::error::Result;
use crate::name;
use super::{reserved_name, Model, RESERVED_ATTRS, RESERVED_COMMANDS, STATE_NAME, STATUS_NAME};
use super::attr::{unsupported, unsupported_prop};
use super::dev::Dev;
#[track_caller]
fn merge_kind<K: PartialEq + Debug>(def: &str, what: &str, current: Option<K>, new: K) -> K {
match current {
Some(current) if current != new => panic!(
"{def}: the {what} is already defined as {current:?}, incompatible with {new:?}"),
_ => new,
}
}
#[track_caller]
fn merge_format(def: &str, current: Option<AttrDataFormat>, new: AttrDataFormat) -> AttrDataFormat {
let Some(current) = current else { return new };
match (¤t, &new) {
(AttrDataFormat::Scalar, AttrDataFormat::Scalar) => AttrDataFormat::Scalar,
(AttrDataFormat::Spectrum(cur), AttrDataFormat::Spectrum(new)) => {
AttrDataFormat::Spectrum(merge_max(def, "maximum size", *cur, *new))
}
(AttrDataFormat::Image(cur_x, cur_y), AttrDataFormat::Image(new_x, new_y)) => {
AttrDataFormat::Image(
merge_max(def, "maximum width", *cur_x, *new_x),
merge_max(def, "maximum height", *cur_y, *new_y),
)
}
_ => panic!(
"{def}: the data format is already defined as {current:?}, incompatible with {new:?}"),
}
}
#[track_caller]
fn merge_max(def: &str, what: &str, current: usize, new: usize) -> usize {
match (current, new) {
(0, new) => new,
(current, 0) => current,
(current, new) if current != new => panic!(
"{def}: the {what} is already defined as {current}, incompatible with {new}"),
(current, _) => current,
}
}
#[track_caller]
fn range_config<T, R>(def: &str, what: &str, range: R) -> AttrRangeConfig
where
T: Into<AttrNumber> + Copy,
R: RangeBounds<T>,
{
AttrRangeConfig {
min: bound_value(def, what, range.start_bound()),
max: bound_value(def, what, range.end_bound()),
}
}
#[track_caller]
fn bound_value<T>(def: &str, what: &str, bound: Bound<&T>) -> Option<AttrNumber>
where
T: Into<AttrNumber> + Copy,
{
match bound {
Bound::Included(value) => Some((*value).into()),
Bound::Unbounded => None,
Bound::Excluded(_) => panic!(
"{def}: the {what} cannot have an exclusive bound, Tango only has inclusive ones"),
}
}
struct HandlerDebug;
impl Debug for HandlerDebug {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("..")
}
}
macro_rules! impl_handler_debug {
($($handler:ident),* $(,)?) => { $(
impl<M: Model> Debug for $handler<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::SharedSync(_) => "SharedSync(..)",
Self::ExclusiveSync(_) => "ExclusiveSync(..)",
Self::SharedAsync(_) => "SharedAsync(..)",
Self::ExclusiveAsync(_) => "ExclusiveAsync(..)",
})
}
}
)* };
}
impl_handler_debug!(StateFn, StatusFn, ExecFn, ReadFn, WriteFn);
pub struct ClassDef<M: Model> {
pub(super) name: Cow<'static, str>,
pub(super) doc: Cow<'static, str>,
pub(super) doc_url: Option<Cow<'static, str>>,
pub(super) init: InitFn<M>,
pub(super) delete: Option<DeleteFn<M>>,
pub(super) state: Option<StateFn<M>>,
pub(super) status: Option<StatusFn<M>>,
pub(super) commands: Vec<Arc<CommandDef<M>>>,
pub(super) attrs: Vec<Arc<AttrDef<M>>>,
pub(super) state_attr: Arc<AttrDef<M>>,
pub(super) status_attr: Arc<AttrDef<M>>,
}
pub enum InitFn<M: Model> {
Sync(Box<dyn for<'ctx> Fn(&'ctx Dev<M>) -> Result<M> + Send + Sync>),
Async(Box<dyn for<'a> Fn(&'a Dev<M>) -> BoxFuture<'a, Result<M>> + Send + Sync>),
}
pub enum DeleteFn<M: Model> {
Sync(Box<dyn for<'ctx> Fn(M, &'ctx Dev<M>) + Send + Sync>),
Async(Box<dyn for<'a> Fn(M, &'a Dev<M>) -> BoxFuture<'a, ()> + Send + Sync>),
}
pub enum StateFn<M: Model> {
SharedSync(Box<dyn for<'m, 'ctx> Fn(&'m M, &'ctx Dev<M>) -> Result<DevState> + Send + Sync>),
ExclusiveSync(Box<dyn for<'m, 'ctx> Fn(&'m mut M, &'ctx Dev<M>) -> Result<DevState> + Send + Sync>),
SharedAsync(Box<dyn for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<DevState>> + Send + Sync>),
ExclusiveAsync(Box<dyn for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<DevState>> + Send + Sync>),
}
pub enum StatusFn<M: Model> {
SharedSync(Box<dyn for<'m, 'ctx> Fn(&'m M, &'ctx Dev<M>) -> Result<Scalar<str>> + Send + Sync>),
ExclusiveSync(Box<dyn for<'m, 'ctx> Fn(&'m mut M, &'ctx Dev<M>) -> Result<Scalar<str>> + Send + Sync>),
SharedAsync(Box<dyn for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>> + Send + Sync>),
ExclusiveAsync(Box<dyn for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>> + Send + Sync>),
}
pub trait HandlerAsync<'a, R, M: Model, O> {
fn call(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<O>>;
}
impl<'a, R, M: Model, O, F, Fut> HandlerAsync<'a, R, M, O> for F
where
F: Fn(R, &'a Dev<M>) -> Fut,
Fut: Future<Output = Result<O>> + Send + 'a,
{
#[inline]
fn call(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<O>> {
Box::pin(self(model, dev))
}
}
pub trait StatusSync<R, M: Model> {
fn call(&self, model: R, dev: &Dev<M>) -> Result<Scalar<str>>;
}
impl<R, M: Model, O, F> StatusSync<R, M> for F
where
F: Fn(R, &Dev<M>) -> Result<O>,
O: Into<Scalar<str>>,
{
#[inline]
fn call(&self, model: R, dev: &Dev<M>) -> Result<Scalar<str>> {
self(model, dev).map(Into::into)
}
}
pub trait StatusAsync<'a, R, M: Model> {
fn call(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>>;
}
impl<'a, R, M: Model, O, F, Fut> StatusAsync<'a, R, M> for F
where
F: Fn(R, &'a Dev<M>) -> Fut,
Fut: Future<Output = Result<O>> + Send + 'a,
O: Into<Scalar<str>>,
{
#[inline]
fn call(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>> {
let fut = self(model, dev);
Box::pin(async move { fut.await.map(Into::into) })
}
}
pub trait InitAsync<'a, M: Model> {
fn call(&self, dev: &'a Dev<M>) -> BoxFuture<'a, Result<M>>;
}
impl<'a, M: Model, F, Fut> InitAsync<'a, M> for F
where
F: Fn(&'a Dev<M>) -> Fut,
Fut: Future<Output = Result<M>> + Send + 'a,
{
#[inline]
fn call(&self, dev: &'a Dev<M>) -> BoxFuture<'a, Result<M>> {
Box::pin(self(dev))
}
}
pub trait DeleteAsync<'a, M: Model> {
fn call(&self, model: M, dev: &'a Dev<M>) -> BoxFuture<'a, ()>;
}
impl<'a, M: Model, F, Fut> DeleteAsync<'a, M> for F
where
F: Fn(M, &'a Dev<M>) -> Fut,
Fut: Future<Output = ()> + Send + 'a,
{
#[inline]
fn call(&self, model: M, dev: &'a Dev<M>) -> BoxFuture<'a, ()> {
Box::pin(self(model, dev))
}
}
impl<M: Model> ClassDef<M> {
pub fn new<N, I>(name: N, init: I) -> Self
where
N: Into<Cow<'static, str>>,
I: for<'ctx> Fn(&'ctx Dev<M>) -> Result<M> + Send + Sync + 'static,
{
Self::with_init(name.into(), InitFn::Sync(Box::new(init)))
}
pub fn new_async_with<N, I>(name: N, init: I) -> Self
where
N: Into<Cow<'static, str>>,
I: for<'a> Fn(&'a Dev<M>) -> BoxFuture<'a, Result<M>> + Send + Sync + 'static,
{
Self::with_init(name.into(), InitFn::Async(Box::new(init)))
}
pub fn new_async<N, I>(name: N, init: I) -> Self
where
N: Into<Cow<'static, str>>,
I: for<'a> InitAsync<'a, M> + Send + Sync + 'static,
{
Self::new_async_with(name, move |dev| init.call(dev))
}
fn with_init(name: Cow<'static, str>, init: InitFn<M>) -> Self {
Self {
name,
doc: Cow::Borrowed("A Tango device"),
doc_url: None,
init,
delete: None,
state: None,
status: None,
commands: Vec::new(),
attrs: Vec::new(),
state_attr: state_attr(AttrConfig {
doc: Some(Cow::Borrowed("The state of the device")),
label: Some(Cow::Borrowed(STATE_NAME)),
..AttrConfig::default()
}),
status_attr: status_attr(AttrConfig {
doc: Some(Cow::Borrowed("The status of the device")),
label: Some(Cow::Borrowed(STATUS_NAME)),
..AttrConfig::default()
}),
}
}
pub fn new_default<N>(name: N) -> Self
where
M: Default,
N: Into<Cow<'static, str>>,
{
Self::new(name, |_ctx| Ok(M::default()))
}
pub fn doc<D>(mut self, doc: D) -> Self
where
D: Into<Cow<'static, str>>,
{
self.doc = doc.into();
self
}
pub fn doc_url<U>(mut self, url: U) -> Self
where
U: Into<Cow<'static, str>>,
{
self.doc_url = Some(url.into());
self
}
pub fn delete<F>(mut self, delete: F) -> Self
where
F: for<'ctx> Fn(M, &'ctx Dev<M>) + Send + Sync + 'static,
{
self.delete = Some(DeleteFn::Sync(Box::new(delete)));
self
}
pub fn delete_async_with<F>(mut self, delete: F) -> Self
where
F: for<'a> Fn(M, &'a Dev<M>) -> BoxFuture<'a, ()> + Send + Sync + 'static,
{
self.delete = Some(DeleteFn::Async(Box::new(delete)));
self
}
pub fn delete_async<F>(self, delete: F) -> Self
where
F: for<'a> DeleteAsync<'a, M> + Send + Sync + 'static,
{
self.delete_async_with(move |model, dev| delete.call(model, dev))
}
pub fn state<F>(mut self, state: F) -> Self
where
F: for<'m, 'ctx> Fn(&'m M, &'ctx Dev<M>) -> Result<DevState> + Send + Sync + 'static,
{
self.state = Some(StateFn::SharedSync(Box::new(state)));
self
}
pub fn state_exclusive<F>(mut self, state: F) -> Self
where
F: for<'m, 'ctx> Fn(&'m mut M, &'ctx Dev<M>) -> Result<DevState> + Send + Sync + 'static,
{
self.state = Some(StateFn::ExclusiveSync(Box::new(state)));
self
}
pub fn state_async_with<F>(mut self, state: F) -> Self
where
F: for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<DevState>> + Send + Sync + 'static,
{
self.state = Some(StateFn::SharedAsync(Box::new(state)));
self
}
pub fn state_async<F>(self, state: F) -> Self
where
F: for<'a> HandlerAsync<'a, &'a M, M, DevState> + Send + Sync + 'static,
{
self.state_async_with(move |model, dev| state.call(model, dev))
}
pub fn state_exclusive_async_with<F>(mut self, state: F) -> Self
where
F: for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<DevState>> + Send + Sync + 'static,
{
self.state = Some(StateFn::ExclusiveAsync(Box::new(state)));
self
}
pub fn state_exclusive_async<F>(self, state: F) -> Self
where
F: for<'a> HandlerAsync<'a, &'a mut M, M, DevState> + Send + Sync + 'static,
{
self.state_exclusive_async_with(move |model, dev| state.call(model, dev))
}
pub fn status_with<F>(mut self, status: F) -> Self
where
F: for<'m, 'ctx> Fn(&'m M, &'ctx Dev<M>) -> Result<Scalar<str>> + Send + Sync + 'static,
{
self.status = Some(StatusFn::SharedSync(Box::new(status)));
self
}
pub fn status<F>(self, status: F) -> Self
where
F: for<'m> StatusSync<&'m M, M> + Send + Sync + 'static,
{
self.status_with(move |model, dev| status.call(model, dev))
}
pub fn status_exclusive_with<F>(mut self, status: F) -> Self
where
F: for<'m, 'ctx> Fn(&'m mut M, &'ctx Dev<M>) -> Result<Scalar<str>> + Send + Sync + 'static,
{
self.status = Some(StatusFn::ExclusiveSync(Box::new(status)));
self
}
pub fn status_exclusive<F>(self, status: F) -> Self
where
F: for<'m> StatusSync<&'m mut M, M> + Send + Sync + 'static,
{
self.status_exclusive_with(move |model, dev| status.call(model, dev))
}
pub fn status_async_with<F>(mut self, status: F) -> Self
where
F: for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>> + Send + Sync + 'static,
{
self.status = Some(StatusFn::SharedAsync(Box::new(status)));
self
}
pub fn status_async<F>(self, status: F) -> Self
where
F: for<'a> StatusAsync<'a, &'a M, M> + Send + Sync + 'static,
{
self.status_async_with(move |model, dev| status.call(model, dev))
}
pub fn status_exclusive_async_with<F>(mut self, status: F) -> Self
where
F: for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<Scalar<str>>> + Send + Sync + 'static,
{
self.status = Some(StatusFn::ExclusiveAsync(Box::new(status)));
self
}
pub fn status_exclusive_async<F>(self, status: F) -> Self
where
F: for<'a> StatusAsync<'a, &'a mut M, M> + Send + Sync + 'static,
{
self.status_exclusive_async_with(move |model, dev| status.call(model, dev))
}
#[track_caller]
pub fn command(mut self, def: CommandDef<M>) -> Self {
check_own_name(&self.name, "command", &def.name, name::check_tango_command_name, RESERVED_COMMANDS);
if self.commands.iter().any(|other| other.name.eq_ignore_ascii_case(&def.name)) {
panic!("{}: the command {} is already defined", self.name, def.name);
}
self.commands.push(Arc::new(def));
self
}
#[track_caller]
pub fn attr(mut self, def: AttrDef<M>) -> Self {
check_own_name(&self.name, "attribute", &def.name, name::check_tango_attr_name, RESERVED_ATTRS);
if let Err(err) = def.check() {
panic!("{}: {err}", self.name);
}
if self.attrs.iter().any(|other| other.name.eq_ignore_ascii_case(&def.name)) {
panic!("{}: the attribute {} is already defined", self.name, def.name);
}
self.attrs.push(Arc::new(def));
self
}
#[track_caller]
pub fn state_config(mut self, config: AttrConfig) -> Self {
self.state_attr = state_attr(config);
self
}
#[track_caller]
pub fn status_config(mut self, config: AttrConfig) -> Self {
self.status_attr = status_attr(config);
self
}
pub fn push_state_events(mut self, change: Option<bool>, archive: Option<bool>) -> Self {
builtin_pushed(&mut self.state_attr, change, archive);
self
}
pub fn push_status_events(mut self, change: Option<bool>, archive: Option<bool>) -> Self {
builtin_pushed(&mut self.status_attr, change, archive);
self
}
}
#[track_caller]
fn state_attr<M: Model>(config: AttrConfig) -> Arc<AttrDef<M>> {
builtin_attr(STATE_NAME, AttrDataKind::State, config)
}
#[track_caller]
fn status_attr<M: Model>(config: AttrConfig) -> Arc<AttrDef<M>> {
builtin_attr(STATUS_NAME, AttrDataKind::String, config)
}
fn builtin_pushed<M: Model>(def: &mut Arc<AttrDef<M>>, change: Option<bool>, archive: Option<bool>) {
let def = Arc::get_mut(def).expect("a class being built is the only owner of its definitions");
def.pushed.change = change;
def.pushed.archive = archive;
}
#[track_caller]
fn builtin_attr<M: Model>(name: &'static str, kind: AttrDataKind, config: AttrConfig) -> Arc<AttrDef<M>> {
let mut def = AttrDef::new(name).kind(kind).scalar();
def.display_level = DisplayLevel::Operator;
def.config = config;
if let Err(err) = def.check() {
panic!("{err}");
}
Arc::new(def)
}
#[track_caller]
fn check_own_name(class: &str, what: &str, name: &str, valid: fn(&str) -> bool, reserved: &[&str]) {
if !valid(name) {
panic!("{class}: the {what} name {name:?} is invalid");
}
if let Some(reserved) = reserved_name(name, reserved) {
panic!("{class}: the {what} {name} cannot be defined by a class, \
{reserved} is answered by the server itself");
}
}
impl<M: Model> Debug for ClassDef<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClassDef")
.field("name", &self.name)
.field("doc", &self.doc)
.field("doc_url", &self.doc_url)
.field("init", &HandlerDebug)
.field("delete", &self.delete.as_ref().map(|_| HandlerDebug))
.field("state", &self.state)
.field("status", &self.status)
.field("commands", &self.commands)
.field("attrs", &self.attrs)
.field("state_attr", &self.state_attr)
.field("status_attr", &self.status_attr)
.finish()
}
}
pub struct CommandDef<M: Model> {
pub(super) name: Cow<'static, str>,
pub(super) exec: Option<ExecFn<M>>,
pub(super) in_kind: Option<CommandDataKind>,
pub(super) in_doc: Cow<'static, str>,
pub(super) out_kind: Option<CommandDataKind>,
pub(super) out_doc: Cow<'static, str>,
pub(super) display_level: DisplayLevel,
pub(super) poll_period: Option<Duration>,
}
pub enum ExecFn<M: Model> {
SharedSync(Box<dyn Fn(&M, &Dev<M>, Value) -> Result<Value> + Send + Sync>),
ExclusiveSync(Box<dyn Fn(&mut M, &Dev<M>, Value) -> Result<Value> + Send + Sync>),
SharedAsync(Box<dyn for<'a> Fn(&'a M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<Value>> + Send + Sync>),
ExclusiveAsync(Box<dyn for<'a> Fn(&'a mut M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<Value>> + Send + Sync>),
}
pub trait ExecShared<'m, M: Model, I> {
const IN_KIND: CommandDataKind;
const OUT_KIND: CommandDataKind;
fn exec(&self, model: &'m M, dev: &Dev<M>, input: Value) -> Result<Value>;
}
impl<'m, M: Model, I, O, F> ExecShared<'m, M, I> for F
where
I: FromValue + CommandData,
O: IntoValue + CommandData,
F: Fn(&'m M, &Dev<M>, I) -> Result<O>,
{
const IN_KIND: CommandDataKind = I::DATA_KIND;
const OUT_KIND: CommandDataKind = O::DATA_KIND;
#[inline]
fn exec(&self, model: &'m M, dev: &Dev<M>, input: Value) -> Result<Value> {
Ok(self(model, dev, I::from_value(input)?)?.into_value())
}
}
pub trait ExecExclusive<'m, M: Model, I> {
const IN_KIND: CommandDataKind;
const OUT_KIND: CommandDataKind;
fn exec(&self, model: &'m mut M, dev: &Dev<M>, input: Value) -> Result<Value>;
}
impl<'m, M: Model, I, O, F> ExecExclusive<'m, M, I> for F
where
I: FromValue + CommandData,
O: IntoValue + CommandData,
F: Fn(&'m mut M, &Dev<M>, I) -> Result<O>,
{
const IN_KIND: CommandDataKind = I::DATA_KIND;
const OUT_KIND: CommandDataKind = O::DATA_KIND;
#[inline]
fn exec(&self, model: &'m mut M, dev: &Dev<M>, input: Value) -> Result<Value> {
Ok(self(model, dev, I::from_value(input)?)?.into_value())
}
}
pub trait ExecAsync<'a, R, M: Model, I> {
const IN_KIND: CommandDataKind;
const OUT_KIND: CommandDataKind;
fn exec(&self, model: R, dev: &'a Dev<M>, input: Value) -> BoxFuture<'a, Result<Value>>;
}
impl<'a, R, M: Model, I, O, F, Fut> ExecAsync<'a, R, M, I> for F
where
I: FromValue + CommandData,
O: IntoValue + CommandData,
F: Fn(R, &'a Dev<M>, I) -> Fut,
Fut: Future<Output = Result<O>> + Send + 'a,
{
const IN_KIND: CommandDataKind = I::DATA_KIND;
const OUT_KIND: CommandDataKind = O::DATA_KIND;
#[inline]
fn exec(&self, model: R, dev: &'a Dev<M>, input: Value) -> BoxFuture<'a, Result<Value>> {
let fut = I::from_value(input).map(|input| self(model, dev, input));
Box::pin(async move { Ok(fut?.await?.into_value()) })
}
}
impl<M: Model> CommandDef<M> {
pub fn new<N>(name: N) -> Self
where
N: Into<Cow<'static, str>>,
{
Self {
name: name.into(),
exec: None,
in_kind: None,
in_doc: Cow::Borrowed(""),
out_kind: None,
out_doc: Cow::Borrowed(""),
display_level: DisplayLevel::default(),
poll_period: None,
}
}
pub fn in_doc<D>(mut self, doc: D) -> Self
where
D: Into<Cow<'static, str>>,
{
self.in_doc = doc.into();
self
}
pub fn out_doc<D>(mut self, doc: D) -> Self
where
D: Into<Cow<'static, str>>,
{
self.out_doc = doc.into();
self
}
pub fn display_level(mut self, level: DisplayLevel) -> Self {
self.display_level = level;
self
}
pub fn polled(mut self, period: Duration) -> Self {
self.poll_period = Some(period);
self
}
#[track_caller]
pub fn in_kind(mut self, kind: CommandDataKind) -> Self {
self.in_kind = Some(merge_kind(&self.name, "argument kind", self.in_kind.take(), kind));
self
}
#[track_caller]
pub fn out_kind(mut self, kind: CommandDataKind) -> Self {
self.out_kind = Some(merge_kind(&self.name, "result kind", self.out_kind.take(), kind));
self
}
pub fn exec_with<F>(mut self, func: F) -> Self
where
F: Fn(&M, &Dev<M>, Value) -> Result<Value> + Send + Sync + 'static,
{
self.exec = Some(ExecFn::SharedSync(Box::new(func)));
self
}
#[track_caller]
pub fn exec<F, I>(self, func: F) -> Self
where
F: for<'m> ExecShared<'m, M, I> + Send + Sync + 'static,
{
self.in_kind(F::IN_KIND)
.out_kind(F::OUT_KIND)
.exec_with(move |model, dev, value| func.exec(model, dev, value))
}
pub fn exec_exclusive_with<F>(mut self, func: F) -> Self
where
F: Fn(&mut M, &Dev<M>, Value) -> Result<Value> + Send + Sync + 'static,
{
self.exec = Some(ExecFn::ExclusiveSync(Box::new(func)));
self
}
#[track_caller]
pub fn exec_exclusive<F, I>(self, func: F) -> Self
where
F: for<'m> ExecExclusive<'m, M, I> + Send + Sync + 'static,
{
self.in_kind(F::IN_KIND)
.out_kind(F::OUT_KIND)
.exec_exclusive_with(move |model, dev, value| func.exec(model, dev, value))
}
pub fn exec_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<Value>> + Send + Sync + 'static,
{
self.exec = Some(ExecFn::SharedAsync(Box::new(func)));
self
}
#[track_caller]
pub fn exec_async<F, I>(self, func: F) -> Self
where
F: for<'a> ExecAsync<'a, &'a M, M, I> + Send + Sync + 'static,
{
self.in_kind(F::IN_KIND)
.out_kind(F::OUT_KIND)
.exec_async_with(move |model, dev, value| func.exec(model, dev, value))
}
pub fn exec_exclusive_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a mut M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<Value>> + Send + Sync + 'static,
{
self.exec = Some(ExecFn::ExclusiveAsync(Box::new(func)));
self
}
#[track_caller]
pub fn exec_exclusive_async<F, I>(self, func: F) -> Self
where
F: for<'a> ExecAsync<'a, &'a mut M, M, I> + Send + Sync + 'static,
{
self.in_kind(F::IN_KIND)
.out_kind(F::OUT_KIND)
.exec_exclusive_async_with(move |model, dev, value| func.exec(model, dev, value))
}
}
impl<M: Model> Debug for CommandDef<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandDef")
.field("name", &self.name)
.field("exec", &self.exec)
.field("in_kind", &self.in_kind)
.field("in_doc", &self.in_doc)
.field("out_kind", &self.out_kind)
.field("out_doc", &self.out_doc)
.field("display_level", &self.display_level)
.field("poll_period", &self.poll_period)
.finish()
}
}
pub struct AttrDef<M: Model> {
pub(super) name: Cow<'static, str>,
pub(super) read: Option<ReadFn<M>>,
pub(super) write: Option<WriteFn<M>>,
pub(super) kind: Option<AttrDataKind>,
pub(super) format: Option<AttrDataFormat>,
pub(super) display_level: DisplayLevel,
pub(super) memorized: Option<AttrMemorized>,
pub(super) config: AttrConfig,
pub(super) poll_period: Option<Duration>,
pub(super) pushed: PushedEvents,
}
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct PushedEvents {
pub change: Option<bool>,
pub archive: Option<bool>,
pub data_ready: bool,
}
pub enum ReadFn<M: Model> {
SharedSync(Box<dyn Fn(&M, &Dev<M>) -> Result<AttrRead<Value>> + Send + Sync>),
ExclusiveSync(Box<dyn Fn(&mut M, &Dev<M>) -> Result<AttrRead<Value>> + Send + Sync>),
SharedAsync(Box<dyn for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>> + Send + Sync>),
ExclusiveAsync(Box<dyn for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>> + Send + Sync>),
}
pub enum WriteFn<M: Model> {
SharedSync(Box<dyn Fn(&M, &Dev<M>, Value) -> Result<()> + Send + Sync>),
ExclusiveSync(Box<dyn Fn(&mut M, &Dev<M>, Value) -> Result<()> + Send + Sync>),
SharedAsync(Box<dyn for<'a> Fn(&'a M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<()>> + Send + Sync>),
ExclusiveAsync(Box<dyn for<'a> Fn(&'a mut M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<()>> + Send + Sync>),
}
pub trait ReadOutput {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn into_read(self) -> AttrRead<Value>;
}
impl<O> ReadOutput for O
where
O: IntoValue + AttrData,
{
const DATA_KIND: AttrDataKind = O::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = O::DATA_FORMAT;
fn into_read(self) -> AttrRead<Value> {
AttrRead {
value: Some(self.into_value()),
w_value: None,
quality: AttrQuality::Valid,
time: SystemTime::now(),
}
}
}
impl<O> ReadOutput for AttrRead<O>
where
O: IntoValue + AttrData,
{
const DATA_KIND: AttrDataKind = O::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = O::DATA_FORMAT;
fn into_read(self) -> AttrRead<Value> {
AttrRead {
value: self.value.map(IntoValue::into_value),
w_value: self.w_value.map(IntoValue::into_value),
quality: self.quality,
time: self.time,
}
}
}
pub trait ReadShared<'m, M: Model> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn read(&self, model: &'m M, dev: &Dev<M>) -> Result<AttrRead<Value>>;
}
impl<'m, M: Model, O, F> ReadShared<'m, M> for F
where
F: Fn(&'m M, &Dev<M>) -> Result<O>,
O: ReadOutput,
{
const DATA_KIND: AttrDataKind = O::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = O::DATA_FORMAT;
#[inline]
fn read(&self, model: &'m M, dev: &Dev<M>) -> Result<AttrRead<Value>> {
Ok(self(model, dev)?.into_read())
}
}
pub trait ReadExclusive<'m, M: Model> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn read(&self, model: &'m mut M, dev: &Dev<M>) -> Result<AttrRead<Value>>;
}
impl<'m, M: Model, O, F> ReadExclusive<'m, M> for F
where
F: Fn(&'m mut M, &Dev<M>) -> Result<O>,
O: ReadOutput,
{
const DATA_KIND: AttrDataKind = O::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = O::DATA_FORMAT;
#[inline]
fn read(&self, model: &'m mut M, dev: &Dev<M>) -> Result<AttrRead<Value>> {
Ok(self(model, dev)?.into_read())
}
}
pub trait WriteShared<M: Model, I> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn write(&self, model: &M, dev: &Dev<M>, value: Value) -> Result<()>;
}
impl<M: Model, I, F> WriteShared<M, I> for F
where
F: Fn(&M, &Dev<M>, I) -> Result<()>,
I: FromValue + AttrData,
{
const DATA_KIND: AttrDataKind = I::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = I::DATA_FORMAT;
#[inline]
fn write(&self, model: &M, dev: &Dev<M>, value: Value) -> Result<()> {
self(model, dev, I::from_value(value)?)
}
}
pub trait WriteExclusive<M: Model, I> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn write(&self, model: &mut M, dev: &Dev<M>, value: Value) -> Result<()>;
}
impl<M: Model, I, F> WriteExclusive<M, I> for F
where
F: Fn(&mut M, &Dev<M>, I) -> Result<()>,
I: FromValue + AttrData,
{
const DATA_KIND: AttrDataKind = I::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = I::DATA_FORMAT;
#[inline]
fn write(&self, model: &mut M, dev: &Dev<M>, value: Value) -> Result<()> {
self(model, dev, I::from_value(value)?)
}
}
pub trait ReadAsync<'a, R, M: Model> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn read(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>>;
}
impl<'a, R, M: Model, O, F, Fut> ReadAsync<'a, R, M> for F
where
F: Fn(R, &'a Dev<M>) -> Fut,
Fut: Future<Output = Result<O>> + Send + 'a,
O: ReadOutput,
{
const DATA_KIND: AttrDataKind = O::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = O::DATA_FORMAT;
#[inline]
fn read(&self, model: R, dev: &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>> {
let fut = self(model, dev);
Box::pin(async move { Ok(fut.await?.into_read()) })
}
}
pub trait WriteAsync<'a, R, M: Model, I> {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
fn write(&self, model: R, dev: &'a Dev<M>, value: Value) -> BoxFuture<'a, Result<()>>;
}
impl<'a, R, M: Model, I, F, Fut> WriteAsync<'a, R, M, I> for F
where
F: Fn(R, &'a Dev<M>, I) -> Fut,
Fut: Future<Output = Result<()>> + Send + 'a,
I: FromValue + AttrData,
{
const DATA_KIND: AttrDataKind = I::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = I::DATA_FORMAT;
#[inline]
fn write(&self, model: R, dev: &'a Dev<M>, value: Value) -> BoxFuture<'a, Result<()>> {
let fut = I::from_value(value).map(|input| self(model, dev, input));
Box::pin(async move { fut?.await })
}
}
impl<M: Model> AttrDef<M> {
pub fn new<N>(name: N) -> Self
where
N: Into<Cow<'static, str>>,
{
Self {
name: name.into(),
read: None,
write: None,
kind: None,
format: None,
display_level: DisplayLevel::default(),
memorized: None,
config: AttrConfig::default(),
poll_period: None,
pushed: PushedEvents::default(),
}
}
pub fn doc<D>(mut self, doc: D) -> Self
where
D: Into<Cow<'static, str>>,
{
self.config.doc = Some(doc.into());
self
}
pub fn label<L>(mut self, label: L) -> Self
where
L: Into<Cow<'static, str>>,
{
self.config.label = Some(label.into());
self
}
pub fn unit<U>(mut self, unit: U) -> Self
where
U: Into<Cow<'static, str>>,
{
self.config.unit = Some(unit.into());
self
}
pub fn standard_unit(mut self, factor: f64) -> Self {
self.config.standard_unit = Some(factor);
self
}
pub fn display_unit<U>(mut self, unit: U) -> Self
where
U: Into<Cow<'static, str>>,
{
self.config.display_unit = Some(unit.into());
self
}
pub fn display_format<F>(mut self, format: F) -> Self
where
F: Into<Cow<'static, str>>,
{
self.config.display_format = Some(format.into());
self
}
pub fn display_level(mut self, level: DisplayLevel) -> Self {
self.display_level = level;
self
}
#[track_caller]
pub fn range<T, R>(mut self, range: R) -> Self
where
T: Into<AttrNumber> + Copy,
R: RangeBounds<T>,
{
self.config.range = range_config(&self.name, "value range", range);
self
}
#[track_caller]
pub fn alarm<T, R>(mut self, range: R) -> Self
where
T: Into<AttrNumber> + Copy,
R: RangeBounds<T>,
{
self.config.alarm = range_config(&self.name, "alarm range", range);
self
}
#[track_caller]
pub fn warning<T, R>(mut self, range: R) -> Self
where
T: Into<AttrNumber> + Copy,
R: RangeBounds<T>,
{
self.config.warning = range_config(&self.name, "warning range", range);
self
}
pub fn delta<V, T>(mut self, value: V, time: T) -> Self
where
V: Into<AttrNumber>,
T: Into<Option<Duration>>,
{
self.config.delta = Some(AttrDeltaConfig {
value: value.into(),
time: time.into(),
});
self
}
pub fn change_event<A, R>(mut self, abs: A, rel: R) -> Self
where
A: Into<Option<f64>>,
R: Into<Option<f64>>,
{
self.config.change_event.abs_change = abs.into();
self.config.change_event.rel_change = rel.into();
self
}
pub fn periodic_event(mut self, period: Duration) -> Self {
self.config.periodic_event.period = Some(period);
self
}
pub fn archive_event<A, R, P>(mut self, abs: A, rel: R, period: P) -> Self
where
A: Into<Option<f64>>,
R: Into<Option<f64>>,
P: Into<Option<Duration>>,
{
self.config.archive_event.abs_change = abs.into();
self.config.archive_event.rel_change = rel.into();
self.config.archive_event.period = period.into();
self
}
pub fn push_change_event(mut self, detect: bool) -> Self {
self.pushed.change = Some(detect);
self
}
pub fn push_archive_event(mut self, detect: bool) -> Self {
self.pushed.archive = Some(detect);
self
}
pub fn push_data_ready_event(mut self) -> Self {
self.pushed.data_ready = true;
self
}
pub fn memorized(mut self, mode: AttrMemorized) -> Self {
self.memorized = Some(mode);
self
}
pub(super) fn check(&self) -> std::result::Result<(), String> {
if let Some(prop) = unsupported_prop(&self.config, self.kind) {
return Err(format!("the attribute {} cannot have the property {:?}, {}",
self.name, prop.name(), unsupported(self.kind)));
}
if self.memorized.is_none() {
return Ok(());
}
let reason = if self.write.is_none() {
"it is not writable"
} else if !matches!(self.format, None | Some(AttrDataFormat::Scalar)) {
"it is not a scalar"
} else if matches!(self.kind, Some(AttrDataKind::State | AttrDataKind::Encoded)) {
"a value of its kind cannot be memorized"
} else {
return Ok(());
};
Err(format!("the attribute {} cannot be memorized, {reason}", self.name))
}
pub fn polled(mut self, period: Duration) -> Self {
self.poll_period = Some(period);
self
}
#[track_caller]
pub fn kind(mut self, kind: AttrDataKind) -> Self {
self.kind = Some(merge_kind(&self.name, "data kind", self.kind.take(), kind));
self
}
#[track_caller]
pub fn format(mut self, format: AttrDataFormat) -> Self {
self.format = Some(merge_format(&self.name, self.format.take(), format));
self
}
#[track_caller]
pub fn scalar(self) -> Self {
self.format(AttrDataFormat::Scalar)
}
#[track_caller]
pub fn spectrum(self, max_size: usize) -> Self {
self.format(AttrDataFormat::Spectrum(max_size))
}
#[track_caller]
pub fn image(self, max_width: usize, max_height: usize) -> Self {
self.format(AttrDataFormat::Image(max_width, max_height))
}
pub fn read_with<F>(mut self, func: F) -> Self
where
F: Fn(&M, &Dev<M>) -> Result<AttrRead<Value>> + Send + Sync + 'static,
{
self.read = Some(ReadFn::SharedSync(Box::new(func)));
self
}
#[track_caller]
pub fn read<F>(self, func: F) -> Self
where
F: for<'m> ReadShared<'m, M> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.read_with(move |model, dev| func.read(model, dev))
}
pub fn read_exclusive_with<F>(mut self, func: F) -> Self
where
F: Fn(&mut M, &Dev<M>) -> Result<AttrRead<Value>> + Send + Sync + 'static,
{
self.read = Some(ReadFn::ExclusiveSync(Box::new(func)));
self
}
#[track_caller]
pub fn read_exclusive<F>(self, func: F) -> Self
where
F: for<'m> ReadExclusive<'m, M> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.read_exclusive_with(move |model, dev| func.read(model, dev))
}
pub fn read_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a M, &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>> + Send + Sync + 'static,
{
self.read = Some(ReadFn::SharedAsync(Box::new(func)));
self
}
#[track_caller]
pub fn read_async<F>(self, func: F) -> Self
where
F: for<'a> ReadAsync<'a, &'a M, M> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.read_async_with(move |model, dev| func.read(model, dev))
}
pub fn read_exclusive_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a mut M, &'a Dev<M>) -> BoxFuture<'a, Result<AttrRead<Value>>> + Send + Sync + 'static,
{
self.read = Some(ReadFn::ExclusiveAsync(Box::new(func)));
self
}
#[track_caller]
pub fn read_exclusive_async<F>(self, func: F) -> Self
where
F: for<'a> ReadAsync<'a, &'a mut M, M> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.read_exclusive_async_with(move |model, dev| func.read(model, dev))
}
pub fn write_with<F>(mut self, func: F) -> Self
where
F: Fn(&M, &Dev<M>, Value) -> Result<()> + Send + Sync + 'static,
{
self.write = Some(WriteFn::SharedSync(Box::new(func)));
self
}
#[track_caller]
pub fn write<F, I>(self, func: F) -> Self
where
F: WriteShared<M, I> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.write_with(move |model, dev, value| func.write(model, dev, value))
}
pub fn write_exclusive_with<F>(mut self, func: F) -> Self
where
F: Fn(&mut M, &Dev<M>, Value) -> Result<()> + Send + Sync + 'static,
{
self.write = Some(WriteFn::ExclusiveSync(Box::new(func)));
self
}
#[track_caller]
pub fn write_exclusive<F, I>(self, func: F) -> Self
where
F: WriteExclusive<M, I> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.write_exclusive_with(move |model, dev, value| func.write(model, dev, value))
}
pub fn write_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<()>> + Send + Sync + 'static,
{
self.write = Some(WriteFn::SharedAsync(Box::new(func)));
self
}
#[track_caller]
pub fn write_async<F, I>(self, func: F) -> Self
where
F: for<'a> WriteAsync<'a, &'a M, M, I> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.write_async_with(move |model, dev, value| func.write(model, dev, value))
}
pub fn write_exclusive_async_with<F>(mut self, func: F) -> Self
where
F: for<'a> Fn(&'a mut M, &'a Dev<M>, Value) -> BoxFuture<'a, Result<()>> + Send + Sync + 'static,
{
self.write = Some(WriteFn::ExclusiveAsync(Box::new(func)));
self
}
#[track_caller]
pub fn write_exclusive_async<F, I>(self, func: F) -> Self
where
F: for<'a> WriteAsync<'a, &'a mut M, M, I> + Send + Sync + 'static,
{
self.kind(F::DATA_KIND)
.format(F::DATA_FORMAT)
.write_exclusive_async_with(move |model, dev, value| func.write(model, dev, value))
}
}
impl<M: Model> Debug for AttrDef<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AttrDef")
.field("name", &self.name)
.field("read", &self.read)
.field("write", &self.write)
.field("kind", &self.kind)
.field("format", &self.format)
.field("config", &self.config)
.field("poll_period", &self.poll_period)
.field("pushed", &self.pushed)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Spectrum;
#[derive(Default)]
struct Model { count: i32, label: String, values: Vec<f64>, labels: Vec<String> }
impl Model {
fn label(&self, _dev: &Dev<Self>, _arg: ()) -> Result<&str> {
Ok(self.label.as_str())
}
fn values(&self, _dev: &Dev<Self>, _arg: ()) -> Result<&[f64]> {
Ok(&self.values)
}
fn set_label(&mut self, _dev: &Dev<Self>, arg: String) -> Result<&str> {
self.label = arg;
Ok(self.label.as_str())
}
fn add(&self, _dev: &Dev<Self>, arg: i32) -> Result<i32> {
Ok(self.count + arg)
}
fn incr(&mut self, _dev: &Dev<Self>, arg: i32) -> Result<i32> {
self.count += arg;
Ok(self.count)
}
fn count(&self, _dev: &Dev<Self>) -> Result<i32> {
Ok(self.count)
}
fn set_count(&mut self, _dev: &Dev<Self>, arg: i32) -> Result<()> {
self.count = arg;
Ok(())
}
fn count_full(&self, _dev: &Dev<Self>) -> Result<AttrRead<i32>> {
Ok(AttrRead {
value: Some(self.count),
w_value: None,
quality: AttrQuality::Valid,
time: SystemTime::now(),
})
}
fn read_label(&self, _dev: &Dev<Self>) -> Result<&str> {
Ok(self.label.as_str())
}
fn read_values(&self, _dev: &Dev<Self>) -> Result<&[f64]> {
Ok(&self.values)
}
fn label_n(&self, _dev: &Dev<Self>, index: usize) -> Result<&str> {
Ok(self.labels[index].as_str())
}
fn tick(&mut self, _dev: &Dev<Self>) -> Result<i32> {
self.count += 1;
Ok(self.count)
}
}
fn version(_model: &Model, _dev: &Dev<Model>, _arg: ()) -> Result<&'static str> {
Ok("1.0.0")
}
#[test]
fn exec_signatures() {
let def = CommandDef::<Model>::new("Add").exec(Model::add);
assert_eq!(def.in_kind, Some(CommandDataKind::Long));
assert_eq!(def.out_kind, Some(CommandDataKind::Long));
let _ = CommandDef::<Model>::new("Incr").exec_exclusive(Model::incr);
let _ = CommandDef::<Model>::new("Status")
.exec(|model: &Model, _dev: &Dev<Model>, (): ()| Ok(model.label.clone()));
let def = CommandDef::<Model>::new("Label")
.out_kind(CommandDataKind::String)
.exec_with(|model: &Model, dev, value| {
Ok(model.label(dev, <()>::from_value(value)?)?.into())
});
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let def = CommandDef::<Model>::new("Sum")
.exec(|_model: &Model, _dev: &Dev<Model>, arg: Vec<f64>| Ok(arg.iter().sum::<f64>()));
assert_eq!(def.in_kind, Some(CommandDataKind::DoubleArray));
assert_eq!(def.out_kind, Some(CommandDataKind::Double));
let _ = CommandDef::<Model>::new("Scale")
.exec(|_model: &Model, _dev: &Dev<Model>, arg: Vec<f64>| Ok(arg.iter().map(|v| v * 2.0).collect::<Vec<f64>>()));
let _ = CommandDef::<Model>::new("Reverse")
.exec(|_model: &Model, _dev: &Dev<Model>, arg: Vec<String>| Ok(arg.into_iter().rev().collect::<Vec<String>>()));
let _ = CommandDef::<Model>::new("Zeros")
.exec(|_model: &Model, _dev: &Dev<Model>, (): ()| Ok(Spectrum::<f64>::from(vec![0.0; 4])));
let def = CommandDef::<Model>::new("Version").exec(version);
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let def = CommandDef::<Model>::new("Label")
.out_doc("The label of the device")
.exec(Model::label);
assert_eq!(def.in_kind, Some(CommandDataKind::Void));
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let def = CommandDef::<Model>::new("Values").exec(Model::values);
assert_eq!(def.out_kind, Some(CommandDataKind::DoubleArray));
let def = CommandDef::<Model>::new("SetLabel").exec_exclusive(Model::set_label);
assert_eq!(def.in_kind, Some(CommandDataKind::String));
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let def = CommandDef::<Model>::new("Status")
.out_kind(CommandDataKind::String)
.exec_with(|model: &Model, _dev, _value| Ok(model.label.as_str().into()));
assert_eq!(def.in_kind, None);
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let _ = CommandDef::<Model>::new("Echo")
.in_kind(CommandDataKind::String)
.out_kind(CommandDataKind::String)
.exec_with(|model: &Model, _dev, value| {
let arg = <&str>::try_from(&value)?;
Ok(format!("{}{arg}", model.label).into())
});
let _ = CommandDef::<Model>::new("SetLabel")
.in_kind(CommandDataKind::String)
.out_kind(CommandDataKind::String)
.exec_exclusive_with(|model: &mut Model, _dev, value| {
model.label = String::try_from(value)?;
Ok(model.label.as_str().into())
});
}
#[test]
fn attr_signatures() {
let def = AttrDef::<Model>::new("Count")
.read(Model::count)
.write_exclusive(Model::set_count);
assert_eq!(def.kind, Some(AttrDataKind::Long));
assert_eq!(def.format, Some(AttrDataFormat::Scalar));
let def = AttrDef::<Model>::new("Label").read(Model::read_label);
assert_eq!(def.kind, Some(AttrDataKind::String));
assert_eq!(def.format, Some(AttrDataFormat::Scalar));
let def = AttrDef::<Model>::new("Values").read(Model::read_values);
assert_eq!(def.kind, Some(AttrDataKind::Double));
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(0)));
let def = AttrDef::<Model>::new("CountFull").read(Model::count_full);
assert_eq!(def.kind, Some(AttrDataKind::Long));
let _ = AttrDef::<Model>::new("Ticks").read_exclusive(Model::tick);
let _ = AttrDef::<Model>::new("Double")
.read(|model: &Model, _dev: &Dev<Model>| Ok(model.count * 2));
let _ = AttrDef::<Model>::new("Raw")
.kind(AttrDataKind::Double)
.format(AttrDataFormat::Spectrum(4096))
.read_with(|model: &Model, _dev| Ok(AttrRead {
value: Some(model.values.as_slice().into()),
w_value: None,
quality: AttrQuality::Valid,
time: SystemTime::now(),
}));
}
#[test]
fn attr_config() {
let def = AttrDef::<Model>::new("Position")
.read(Model::count)
.doc("The position of the axis")
.label("Position")
.unit("mm")
.standard_unit(1e-3)
.display_unit("mm")
.display_format("%8.3f")
.display_level(DisplayLevel::Expert)
.delta(0.5, Duration::from_millis(200))
.change_event(0.001, None)
.periodic_event(Duration::from_secs(1))
.archive_event(0.1, None, Duration::from_secs(10))
.memorized(AttrMemorized::RestoreAndWrite)
.polled(Duration::from_millis(100));
assert_eq!(def.config.doc.as_deref(), Some("The position of the axis"));
assert_eq!(def.config.label.as_deref(), Some("Position"));
assert_eq!(def.config.unit.as_deref(), Some("mm"));
assert_eq!(def.config.standard_unit, Some(1e-3));
assert_eq!(def.config.display_unit.as_deref(), Some("mm"));
assert_eq!(def.config.display_format.as_deref(), Some("%8.3f"));
assert_eq!(def.display_level, DisplayLevel::Expert);
assert_eq!(def.config.delta, Some(AttrDeltaConfig {
value: AttrNumber::Double(0.5),
time: Some(Duration::from_millis(200)),
}));
assert_eq!(def.config.change_event.abs_change, Some(0.001));
assert_eq!(def.config.change_event.rel_change, None);
assert_eq!(def.config.periodic_event.period, Some(Duration::from_secs(1)));
assert_eq!(def.config.archive_event.abs_change, Some(0.1));
assert_eq!(def.config.archive_event.period, Some(Duration::from_secs(10)));
assert_eq!(def.memorized, Some(AttrMemorized::RestoreAndWrite));
assert_eq!(def.poll_period, Some(Duration::from_millis(100)));
let def = AttrDef::<Model>::new("Count").read(Model::count);
assert_eq!(def.config, AttrConfig::default());
assert_eq!(def.poll_period, None);
}
#[test]
fn attr_ranges() {
let def = AttrDef::<Model>::new("Position")
.read(Model::count)
.range(-100.0..=100.0)
.warning(-90.5..)
.alarm(..=95.0);
assert_eq!(def.config.range, AttrRangeConfig {
min: Some(AttrNumber::Double(-100.0)),
max: Some(AttrNumber::Double(100.0)),
});
assert_eq!(def.config.warning, AttrRangeConfig {
min: Some(AttrNumber::Double(-90.5)),
max: None,
});
assert_eq!(def.config.alarm, AttrRangeConfig {
min: None,
max: Some(AttrNumber::Double(95.0)),
});
let def = AttrDef::<Model>::new("Count")
.read(Model::count)
.range(-8..=8i32)
.alarm(..=u64::MAX);
assert_eq!(def.config.range, AttrRangeConfig {
min: Some(AttrNumber::Long(-8)),
max: Some(AttrNumber::Long(8)),
});
assert_eq!(def.config.alarm.max, Some(AttrNumber::ULong(u64::MAX)));
assert_ne!(AttrNumber::ULong(u64::MAX), AttrNumber::ULong(u64::MAX - 1));
assert_eq!(AttrNumber::ULong(u64::MAX).as_f64(), AttrNumber::ULong(u64::MAX - 1).as_f64());
assert_eq!(AttrNumber::Long(2), AttrNumber::Double(2.0));
assert!(AttrNumber::Long(-1) < AttrNumber::ULong(0));
assert!(AttrNumber::ULong(u64::MAX) > AttrNumber::ULong(u64::MAX - 1));
assert_ne!(AttrNumber::Double(f64::NAN), AttrNumber::Double(f64::NAN));
}
#[test]
#[should_panic(expected = "the value range cannot have an exclusive bound")]
fn exclusive_range_bound() {
let _ = AttrDef::<Model>::new("Position").range(-100.0..100.0);
}
#[test]
fn attr_layouts() {
let def = AttrDef::<Model>::new("Count").read(Model::count).scalar();
assert_eq!(def.format, Some(AttrDataFormat::Scalar));
let def = AttrDef::<Model>::new("Values").read(Model::read_values).spectrum(4096);
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(4096)));
let def = AttrDef::<Model>::new("Values").spectrum(4096).read(Model::read_values);
assert_eq!(def.kind, Some(AttrDataKind::Double));
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(4096)));
let def = AttrDef::<Model>::new("Frame")
.kind(AttrDataKind::Double)
.image(640, 480);
assert_eq!(def.format, Some(AttrDataFormat::Image(640, 480)));
let def = AttrDef::<Model>::new("Values").read(Model::read_values).spectrum(0);
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(0)));
}
#[test]
#[should_panic(expected = "the data format is already defined as Scalar")]
fn incompatible_layout() {
let _ = AttrDef::<Model>::new("Count").read(Model::count).spectrum(8);
}
#[test]
fn command_config() {
let def = CommandDef::<Model>::new("Add")
.exec(Model::add)
.display_level(DisplayLevel::Expert)
.polled(Duration::from_secs(1));
assert_eq!(def.display_level, DisplayLevel::Expert);
assert_eq!(def.poll_period, Some(Duration::from_secs(1)));
let def = CommandDef::<Model>::new("Add").exec(Model::add);
assert_eq!(def.display_level, DisplayLevel::Operator);
assert_eq!(def.poll_period, None);
}
#[test]
fn class_doc() {
let def = ClassDef::<Model>::new_default("Motor").doc("A single axis");
assert_eq!(def.doc, "A single axis");
let def = ClassDef::<Model>::new_default("Motor");
assert_eq!(def.doc, "A Tango device");
}
#[test]
fn compatible_kinds() {
let def = CommandDef::<Model>::new("Add")
.in_kind(CommandDataKind::Long)
.exec(Model::add)
.out_kind(CommandDataKind::Long);
assert_eq!(def.in_kind, Some(CommandDataKind::Long));
assert_eq!(def.out_kind, Some(CommandDataKind::Long));
let def = AttrDef::<Model>::new("Values")
.read(Model::read_values)
.format(AttrDataFormat::Spectrum(4096));
assert_eq!(def.kind, Some(AttrDataKind::Double));
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(4096)));
let def = AttrDef::<Model>::new("Values")
.format(AttrDataFormat::Spectrum(4096))
.read(Model::read_values);
assert_eq!(def.format, Some(AttrDataFormat::Spectrum(4096)));
}
#[test]
#[should_panic(expected = "the argument kind is already defined as String")]
fn incompatible_in_kind() {
let _ = CommandDef::<Model>::new("Add")
.in_kind(CommandDataKind::String)
.exec(Model::add);
}
#[test]
#[should_panic(expected = "the result kind is already defined as Long")]
fn incompatible_out_kind() {
let _ = CommandDef::<Model>::new("Add")
.exec(Model::add)
.out_kind(CommandDataKind::Long64);
}
#[test]
#[should_panic(expected = "the data kind is already defined as String")]
fn incompatible_attr_kind() {
let _ = AttrDef::<Model>::new("Label")
.read(Model::read_label)
.write_exclusive(Model::set_count);
}
#[test]
#[should_panic(expected = "the data format is already defined as Scalar")]
fn incompatible_attr_format() {
let _ = AttrDef::<Model>::new("Count")
.read(Model::count)
.format(AttrDataFormat::Spectrum(8));
}
#[test]
#[should_panic(expected = "the maximum size is already defined as 8")]
fn incompatible_attr_max() {
let _ = AttrDef::<Model>::new("Values")
.format(AttrDataFormat::Spectrum(8))
.format(AttrDataFormat::Spectrum(4096));
}
#[test]
#[should_panic(expected = "Test: the attribute Label cannot have the property \"max_alarm\", not supported by an attribute of type String")]
fn unsupported_attr_prop() {
let _ = ClassDef::<Model>::new_default("Test")
.attr(AttrDef::new("Label")
.read(Model::read_label)
.alarm(..=5));
}
#[test]
#[should_panic(expected = "Test: the command name \"Do it\" is invalid")]
fn invalid_command_name() {
let _ = ClassDef::<Model>::new_default("Test").command(CommandDef::new("Do it"));
}
#[test]
#[should_panic(expected = "Test: the attribute name \"_Speed\" is invalid")]
fn invalid_attr_name() {
let _ = ClassDef::<Model>::new_default("Test").attr(AttrDef::new("_Speed").kind(AttrDataKind::Double).scalar());
}
#[test]
#[should_panic(expected = "the attribute State cannot have the property \"min_value\", not supported by an attribute of type State")]
fn unsupported_state_prop() {
let _ = ClassDef::<Model>::new_default("Test")
.state_config(AttrConfig { range: AttrRangeConfig { min: Some(AttrNumber::Long(0)), max: None }, ..AttrConfig::default() });
}
#[test]
#[should_panic(expected = "the attribute Count cannot be memorized, it is not writable")]
fn memorized_read_only() {
let _ = ClassDef::<Model>::new_default("Test")
.attr(AttrDef::new("Count")
.read(Model::count)
.memorized(AttrMemorized::RestoreSetPoint));
}
#[test]
#[should_panic(expected = "the attribute Values cannot be memorized, it is not a scalar")]
fn memorized_spectrum() {
let _ = ClassDef::<Model>::new_default("Test")
.attr(AttrDef::new("Values")
.memorized(AttrMemorized::RestoreAndWrite)
.kind(AttrDataKind::Double)
.format(AttrDataFormat::Spectrum(8))
.write_with(|_model: &Model, _dev, _value| Ok(())));
}
#[test]
fn closures_borrowing() {
for index in 0..3 {
let def = CommandDef::<Model>::new(format!("Label{index}"))
.out_kind(CommandDataKind::String)
.exec_with(move |model: &Model, dev, _value| {
Ok(model.label_n(dev, index)?.into())
});
assert_eq!(def.out_kind, Some(CommandDataKind::String));
}
let _ = CommandDef::<Model>::new("Push")
.in_kind(CommandDataKind::String)
.out_kind(CommandDataKind::String)
.exec_exclusive_with(|model: &mut Model, _dev, value| {
model.labels.push(String::try_from(value)?);
Ok(model.labels.last().unwrap().as_str().into())
});
for index in 0..3 {
let def = AttrDef::<Model>::new(format!("Label{index}"))
.kind(AttrDataKind::String)
.format(AttrDataFormat::Scalar)
.read_with(move |model: &Model, dev| {
Ok(AttrRead {
value: Some(model.label_n(dev, index)?.into()),
w_value: None,
quality: AttrQuality::Valid,
time: SystemTime::now(),
})
});
assert_eq!(def.kind, Some(AttrDataKind::String));
}
}
struct Owning { values: Vec<f64> }
impl Owning {
fn len(&self, _dev: &Dev<Self>, _arg: ()) -> Result<i32> { Ok(self.values.len() as i32) }
fn first(&self, _dev: &Dev<Self>) -> Result<f64> { Ok(self.values[0]) }
}
fn owning_defs(prefix: &str) -> (CommandDef<Owning>, AttrDef<Owning>) {
let command = CommandDef::<Owning>::new("Len")
.exec(Owning::len)
.out_doc(prefix.to_owned());
let attr = AttrDef::<Owning>::new("First")
.read(Owning::first)
.write_with({
let prefix = prefix.to_owned();
move |_model: &Owning, _dev, _value| {
let _ = &prefix;
Ok(())
}
});
(command, attr)
}
#[test]
fn model_owned() {
let values = [1.0, 2.0, 3.0];
let prefix = format!("{} values", values.len());
let (command, attr) = owning_defs(&prefix);
drop(prefix);
assert_eq!(command.out_kind, Some(CommandDataKind::Long));
assert_eq!(attr.kind, Some(AttrDataKind::Double));
}
struct AsyncModel { count: i32, label: String }
impl AsyncModel {
async fn add(&self, _dev: &Dev<Self>, arg: i32) -> Result<i32> {
Ok(self.count + arg)
}
async fn label(&self, _dev: &Dev<Self>, _arg: ()) -> Result<&str> {
Ok(self.label.as_str())
}
async fn incr(&mut self, _dev: &Dev<Self>, arg: i32) -> Result<i32> {
self.count += arg;
Ok(self.count)
}
async fn count(&self, _dev: &Dev<Self>) -> Result<i32> {
Ok(self.count)
}
async fn set_count(&mut self, _dev: &Dev<Self>, arg: i32) -> Result<()> {
self.count = arg;
Ok(())
}
async fn read_label(&self, _dev: &Dev<Self>) -> Result<&str> {
Ok(self.label.as_str())
}
async fn check_label(&self, _dev: &Dev<Self>, arg: String) -> Result<()> {
let _ = arg.len();
Ok(())
}
async fn tick(&mut self, _dev: &Dev<Self>) -> Result<i32> {
self.count += 1;
Ok(self.count)
}
async fn state(&self, _dev: &Dev<Self>) -> Result<DevState> {
Ok(DevState::On)
}
async fn status(&mut self, _dev: &Dev<Self>) -> Result<&str> {
Ok(&self.label)
}
fn status_label(&self, _dev: &Dev<Self>) -> Result<&str> {
Ok(&self.label)
}
}
#[test]
fn exec_async_signatures() {
let def = CommandDef::<AsyncModel>::new("Add").exec_async(AsyncModel::add);
assert_eq!(def.in_kind, Some(CommandDataKind::Long));
assert_eq!(def.out_kind, Some(CommandDataKind::Long));
assert!(matches!(def.exec, Some(ExecFn::SharedAsync(_))));
let def = CommandDef::<AsyncModel>::new("Label").exec_async(AsyncModel::label);
assert_eq!(def.in_kind, Some(CommandDataKind::Void));
assert_eq!(def.out_kind, Some(CommandDataKind::String));
let def = CommandDef::<AsyncModel>::new("Incr").exec_exclusive_async(AsyncModel::incr);
assert_eq!(def.in_kind, Some(CommandDataKind::Long));
assert!(matches!(def.exec, Some(ExecFn::ExclusiveAsync(_))));
let def = CommandDef::<AsyncModel>::new("Echo")
.in_kind(CommandDataKind::String)
.out_kind(CommandDataKind::String)
.exec_async_with(|model: &AsyncModel, _dev, value| Box::pin(async move {
let arg = <&str>::try_from(&value)?;
Ok(format!("{}{arg}", model.label).into())
}));
assert_eq!(def.in_kind, Some(CommandDataKind::String));
assert!(matches!(def.exec, Some(ExecFn::SharedAsync(_))));
let def = CommandDef::<AsyncModel>::new("SetLabel")
.exec_exclusive_async_with(|model: &mut AsyncModel, _dev, value| Box::pin(async move {
model.label = String::try_from(value)?;
Ok(model.label.as_str().into())
}));
assert_eq!(def.in_kind, None);
assert_eq!(def.out_kind, None);
assert!(matches!(def.exec, Some(ExecFn::ExclusiveAsync(_))));
}
#[test]
fn attr_async_signatures() {
let def = AttrDef::<AsyncModel>::new("Count")
.read_async(AsyncModel::count)
.write_exclusive_async(AsyncModel::set_count);
assert_eq!(def.kind, Some(AttrDataKind::Long));
assert_eq!(def.format, Some(AttrDataFormat::Scalar));
assert!(matches!(def.read, Some(ReadFn::SharedAsync(_))));
assert!(matches!(def.write, Some(WriteFn::ExclusiveAsync(_))));
let def = AttrDef::<AsyncModel>::new("Label")
.read_async(AsyncModel::read_label)
.write_async(AsyncModel::check_label);
assert_eq!(def.kind, Some(AttrDataKind::String));
assert!(matches!(def.write, Some(WriteFn::SharedAsync(_))));
let def = AttrDef::<AsyncModel>::new("Ticks").read_exclusive_async(AsyncModel::tick);
assert!(matches!(def.read, Some(ReadFn::ExclusiveAsync(_))));
let def = AttrDef::<AsyncModel>::new("Raw")
.kind(AttrDataKind::Long)
.scalar()
.read_async_with(|model: &AsyncModel, _dev| Box::pin(async move {
Ok(AttrRead {
value: Some(model.count.into()),
w_value: None,
quality: AttrQuality::Valid,
time: SystemTime::now(),
})
}))
.write_exclusive_async_with(|model: &mut AsyncModel, _dev, value| Box::pin(async move {
model.count = i32::try_from(value)?;
Ok(())
}));
assert!(matches!(def.read, Some(ReadFn::SharedAsync(_))));
assert!(matches!(def.write, Some(WriteFn::ExclusiveAsync(_))));
}
#[test]
#[should_panic(expected = "the data kind is already defined as String")]
fn incompatible_attr_async_kind() {
let _ = AttrDef::<AsyncModel>::new("Label")
.read_async(AsyncModel::read_label)
.write_exclusive_async(AsyncModel::set_count);
}
#[test]
fn class_async_handlers() {
let def = ClassDef::<AsyncModel>::new("Motor", |_dev| Ok(AsyncModel { count: 0, label: String::new() }))
.state_async(AsyncModel::state)
.status_exclusive_async(AsyncModel::status);
assert!(matches!(def.state, Some(StateFn::SharedAsync(_))));
assert!(matches!(def.status, Some(StatusFn::ExclusiveAsync(_))));
let def = def
.state_exclusive_async_with(|_model: &mut AsyncModel, _dev| Box::pin(async { Ok(DevState::Moving) }))
.status_async_with(|model: &AsyncModel, _dev| Box::pin(async move { Ok(Scalar::from(model.label.as_str())) }));
assert!(matches!(def.state, Some(StateFn::ExclusiveAsync(_))));
assert!(matches!(def.status, Some(StatusFn::SharedAsync(_))));
}
#[test]
fn class_status_handlers() {
let def = ClassDef::<AsyncModel>::new("Motor", |_dev| Ok(AsyncModel { count: 0, label: String::new() }))
.status(AsyncModel::status_label);
assert!(matches!(def.status, Some(StatusFn::SharedSync(_))));
let def = def.status_exclusive(|model: &mut AsyncModel, _dev: &Dev<AsyncModel>| Ok(format!("Count is {}", model.count)));
assert!(matches!(def.status, Some(StatusFn::ExclusiveSync(_))));
}
}