1use std::time::{Duration, Instant, SystemTime};
4use std::sync::{Arc, Mutex};
5use std::cmp::Ordering;
6use std::borrow::Cow;
7
8use tracing::warn;
9
10use crate::dev::{AttrConfig, AttrDeltaConfig, AttrInfo, AttrMemorized, AttrMode, AttrNumber, AttrQuality, AttrRangeConfig, AttrRead,
11 AttrValueEvent, Named};
12use crate::value::{AttrDataKind, DevEnum, Spectrum, Value, ValueFormat};
13use crate::error::{reason, Error, ErrorKind, Result};
14use crate::buf::Element;
15
16use super::def::{AttrDef, ReadFn, WriteFn};
17use super::dev::{Access, Dev, ModelRef};
18use super::Model;
19
20
21pub struct Attr<M: Model> {
25 def: Arc<AttrDef<M>>,
27 w_value: Mutex<Option<Value>>,
35 w_time: Mutex<Option<Instant>>,
39 setup: Mutex<AttrSetup>,
42}
43
44struct AttrSetup {
47 config: AttrConfig,
50 error: Option<String>,
55 loaded: bool,
57 restore: Option<Value>,
60 mem_error: Option<String>,
64}
65
66impl<M: Model> Attr<M> {
67
68 pub(super) fn new(def: Arc<AttrDef<M>>) -> Self {
70
71 let setup = AttrSetup {
72 config: def.config.clone(),
73 error: None,
74 loaded: false,
75 restore: None,
76 mem_error: None,
77 };
78
79 Self {
80 def,
81 w_value: Mutex::new(None),
82 w_time: Mutex::new(None),
83 setup: Mutex::new(setup),
84 }
85
86 }
87
88 #[inline]
90 pub fn name(&self) -> &str {
91 &self.def.name
92 }
93
94 pub fn config(&self) -> AttrConfig {
96 self.setup.lock().unwrap().config.clone()
97 }
98
99 pub(super) fn is_loaded(&self) -> bool {
102 self.setup.lock().unwrap().loaded
103 }
104
105 pub(super) fn has_alarms(&self) -> bool {
109 let config = &self.setup.lock().unwrap().config;
110 config.alarm.is_set() || config.warning.is_set() || config.delta.is_some()
111 }
112
113 pub(super) fn load_config(&self, dev: &Dev<M>, class: PropsResult<'_>, device: PropsResult<'_>) {
127
128 let mut loader = ConfigLoader::new(&self.def.config, self.def.kind);
129 loader.apply_level("class", class);
130 loader.apply_level("device", device);
131
132 let (config, mut error) = loader.finish();
133 if let Some(incoherence) = incoherence(&config) {
134 error = Some(match error {
135 Some(error) => format!("{error}, {incoherence}"),
136 None => incoherence,
137 });
138 }
139
140 if let Some(error) = &error {
141 warn!(dev = %dev.name(), attr = %self.name(), err = %error, "Failed to configure the attribute from the database");
142 }
143
144 let mut setup = self.setup.lock().unwrap();
145 if setup.loaded {
146 setup.config = config;
147 setup.error = error;
148 return;
149 }
150
151 let mut restore = None;
152 let mut mem_error = None;
153
154 let mem_value = device.ok()
156 .and_then(|props| props
157 .iter()
158 .find(|prop| prop.name.value().eq_ignore_ascii_case(MEM_VALUE)));
159
160 if let (Some(mode), Some(mem_value)) = (self.def.memorized, mem_value) {
161 match parse_mem_value(self.def.kind.as_ref(), &mem_value.value) {
162 Ok(None) => {}
163 Ok(Some(value)) => match mode {
164 AttrMemorized::RestoreSetPoint => *self.w_value.lock().unwrap() = Some(value),
165 AttrMemorized::RestoreAndWrite => restore = Some(value),
166 }
167 Err(err) => {
168 warn!(dev = %dev.name(), attr = %self.name(), %err, "Failed to parse the memorized value");
169 mem_error = Some(format!("the memorized value cannot be parsed: {err}"));
170 }
171 }
172 }
173
174 *setup = AttrSetup {
175 config,
176 error,
177 loaded: true,
178 restore,
179 mem_error,
180 };
181
182 }
183
184 pub(super) fn change_config(&self, class: &[Named<Spectrum<str>>], requested: &AttrConfig) -> std::result::Result<ConfigChanges, String> {
210
211 let mut config = ConfigLoader::new(&self.def.config, self.def.kind);
215 config.apply_level("class", Ok(class));
216 let (mut config, class_error) = config.finish();
217
218 if let Some(prop) = unsupported_prop(requested, self.def.kind) {
219 return Err(format!("its property {:?} is {}", prop.name(), unsupported(self.def.kind)));
220 }
221
222 let w_value = self.def.memorized.and_then(|_| self.w_value.lock().unwrap().clone());
223
224 let mut setup = self.setup.lock().unwrap();
225 let mut changes = ConfigChanges::default();
226
227 for prop in ConfigProp::ALL {
228
229 let target = prop_value(requested, prop);
230 let now = prop_value(&setup.config, prop);
231 let inherited = prop_value(&config, prop);
232
233 if target.is_unset() || target == inherited {
234 if now != inherited {
236 changes.del.push(prop);
237 }
238 } else {
239 if target != now {
240 changes.put.push((prop, target.to_values()));
241 }
242 copy_prop(&mut config, requested, prop);
243 }
244
245 }
246
247 if let Some(incoherence) = incoherence(&config) {
248 return Err(incoherence);
249 }
250
251 if let Some(w_value) = &w_value {
252 if any_number(w_value, |number| outside(&config.range, number)) {
253 return Err("its memorized value would be outside of its range".to_string());
254 }
255 }
256
257 setup.config = config;
260 setup.error = class_error;
261 Ok(changes)
262
263 }
264
265 pub(super) async fn restore(&self, dev: &Dev<M>, model: &mut M) {
273
274 let Some(value) = self.setup.lock().unwrap().restore.take() else {
275 return;
276 };
277
278 if self.check_config(dev, false).is_err() {
279 return;
280 }
281
282 if let Err(err) = self.write_inner(dev, &mut ModelRef::Exclusive(model), value).await {
283 warn!(dev = %dev.name(), attr = %self.name(), %err, "Failed to write the memorized value back");
284 self.setup.lock().unwrap().mem_error = Some(format!("the memorized value cannot be \
285 written back: {}", err.description()));
286 }
287
288 }
289
290 fn check_config(&self, dev: &Dev<M>, check_mem: bool) -> Result<()> {
295
296 let setup = self.setup.lock().unwrap();
297
298 if let Some(error) = &setup.error {
299 return Err(Error::new(ErrorKind::Reason(reason::API_AttrConfig))
300 .with_description(format!("The attribute {:?} of the device {:?} has a wrong \
301 configuration in the database: {error}", self.name(), dev.name())));
302 }
303
304 if check_mem && let Some(error) = &setup.mem_error {
305 return Err(Error::new(ErrorKind::Reason(reason::API_MemAttFailedDuringInit))
306 .with_description(format!("The attribute {:?} of the device {:?} has no set \
307 point, {error}, it has to be written again", self.name(), dev.name())));
308 }
309
310 Ok(())
311
312 }
313
314 #[inline]
316 pub fn is_memorized(&self) -> bool {
317 self.def.memorized.is_some()
318 }
319
320 pub(super) async fn set_w_value(&self, dev: &Dev<M>, value: Value) -> Result<()> {
328 *self.w_value.lock().unwrap() = Some(value.clone());
329 self.setup.lock().unwrap().mem_error = None;
330 self.store(dev, &value).await
331 }
332
333 async fn store(&self, dev: &Dev<M>, value: &Value) -> Result<()> {
335 if self.def.memorized.is_none() {
336 return Ok(());
337 }
338 let mem_value = mem_value_str(value)
339 .map_err(|err| Error::new(ErrorKind::Reason(reason::API_AttrConfig))
340 .with_description(format!("The attribute {:?} of the device {:?} cannot be \
341 memorized: {err}", self.name(), dev.name())))?;
342 dev.server().database()
343 .set_dev_attr_prop_async(dev.name(), (self.name(), (MEM_VALUE, mem_value)))
344 .await
345 }
346
347 pub(super) fn read_access(&self) -> Access {
349 match &self.def.read {
350 Some(ReadFn::ExclusiveSync(_) | ReadFn::ExclusiveAsync(_)) => Access::Exclusive,
351 _ => Access::Shared,
352 }
353 }
354
355 pub(super) fn write_access(&self) -> Access {
357 match &self.def.write {
358 Some(WriteFn::ExclusiveSync(_) | WriteFn::ExclusiveAsync(_)) => Access::Exclusive,
359 _ => Access::Shared,
360 }
361 }
362
363 pub(super) fn read_may_block(&self) -> bool {
366 matches!(&self.def.read, Some(ReadFn::SharedSync(_) | ReadFn::ExclusiveSync(_)))
367 }
368
369 pub(super) fn write_may_block(&self) -> bool {
371 matches!(&self.def.write, Some(WriteFn::SharedSync(_) | WriteFn::ExclusiveSync(_)))
372 }
373
374 pub fn info(&self) -> Result<AttrInfo> {
387 self.info_as(match (&self.def.read, &self.def.write) {
388 (Some(_), Some(_)) => AttrMode::ReadWrite,
389 (Some(_), None) => AttrMode::Read,
390 (None, Some(_)) => AttrMode::Write,
391 (None, None) => AttrMode::ReadWrite,
394 })
395 }
396
397 fn no_handler_err(&self, dev: &Dev<M>) -> Error {
400 Error::not_implemented()
401 .with_description(format!("The attribute {} of the device {} has no handler defined",
402 self.name(), dev.name()))
403 }
404
405 pub(super) fn check_push_event(&self, dev: &Dev<M>, event: AttrValueEvent) -> Result<()> {
411
412 let pushed = match event {
413 AttrValueEvent::Change => self.def.pushed.change.is_some(),
414 AttrValueEvent::Archive => self.def.pushed.archive.is_some(),
415 AttrValueEvent::Periodic => false,
416 AttrValueEvent::User |
417 AttrValueEvent::Alarm => true,
418 };
419
420 if pushed {
421 return Ok(());
422 }
423
424 Err(Error::new(ErrorKind::Reason(reason::API_AttributePollingNotStarted))
425 .with_description(format!("The polling (necessary to send events) for the attribute {} \
426 of the device {} is not started, and the device does not push its {event:?} events",
427 self.name(),
428 dev.name())))
429
430 }
431
432 pub(super) fn info_as(&self, mode: AttrMode) -> Result<AttrInfo> {
436 Ok(AttrInfo {
437 config: self.config(),
438 mode,
439 data_kind: self.def.kind
440 .ok_or_else(|| Error::new(ErrorKind::Internal)
441 .with_description(format!("The attribute {} does not advertise the kind \
442 of its data", self.name())))?,
443 data_format: self.def.format
444 .ok_or_else(|| Error::new(ErrorKind::Internal)
445 .with_description(format!("The attribute {} does not advertise the \
446 format of its data", self.name())))?,
447 display_level: self.def.display_level,
448 memorized: self.def.memorized,
449 })
450 }
451
452 fn alarm(&self, read: &AttrRead<Value>) -> Option<AttrAlarm> {
461
462 let value = match (read.quality, &read.value) {
463 (AttrQuality::Alarm | AttrQuality::Warning, _) => return Some(AttrAlarm::Device(read.quality)),
464 (AttrQuality::Valid, Some(value)) => value,
465 _ => return None,
466 };
467
468 let (alarm, warning, delta) = {
469 let config = &self.setup.lock().unwrap().config;
470 (config.alarm, config.warning, config.delta)
471 };
472
473 if any_number(value, |number| outside(&alarm, number)) {
474 return Some(AttrAlarm::Alarm);
475 }
476
477 if any_number(value, |number| outside(&warning, number)) {
478 return Some(AttrAlarm::Warning);
479 }
480
481 if let (Some(delta), Some(w_value)) = (delta, &read.w_value) {
482 let written = *self.w_time.lock().unwrap();
483 let settled = written.is_some_and(|time| time.elapsed() >= delta.time.unwrap_or_default());
484 if settled && drifted(value, w_value, delta.value) {
485 return Some(AttrAlarm::Delta);
486 }
487 }
488
489 None
490
491 }
492
493 pub(super) async fn read_with_alarm(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>) -> Result<(AttrRead<Value>, Option<AttrAlarm>)> {
496 let mut read = self.read_inner(dev, model).await?;
497 let alarm = self.alarm(&read);
498 if let Some(alarm) = alarm {
499 read.quality = alarm.quality();
500 }
501 Ok((read, alarm))
502 }
503
504 pub(super) async fn read(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>) -> Result<AttrRead<Value>> {
508 self.read_with_alarm(dev, model).await.map(|(read, _)| read)
509 }
510
511 async fn read_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>) -> Result<AttrRead<Value>> {
513
514 self.check_config(dev, true)?;
515
516 let mut read = match &self.def.read {
517 Some(ReadFn::SharedSync(func)) => func(model.shared(), dev)?,
518 Some(ReadFn::ExclusiveSync(func)) => func(model.exclusive()?, dev)?,
519 Some(ReadFn::SharedAsync(func)) => func(model.shared(), dev).await?,
520 Some(ReadFn::ExclusiveAsync(func)) => func(model.exclusive()?, dev).await?,
521 None if self.def.write.is_none() => return Err(self.no_handler_err(dev)),
522 None => AttrRead {
526 value: None,
527 w_value: None,
528 quality: AttrQuality::Valid,
529 time: SystemTime::now(),
530 },
531 };
532
533 self.merge_w_value(&mut read);
534 Ok(read)
535
536 }
537
538 pub(super) fn merge_w_value(&self, read: &mut AttrRead<Value>) {
543 match &read.w_value {
544 None => read.w_value = self.w_value.lock().unwrap().clone(),
545 Some(w_value) => *self.w_value.lock().unwrap() = Some(w_value.clone()),
546 }
547 }
548
549 pub(super) async fn write(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
557 self.check_config(dev, false)?;
558 self.write_inner(dev, model, value.clone()).await?;
559 self.store(dev, &value).await
560 }
561
562 async fn write_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
565
566 self.check_limits(dev, &value)?;
567
568 match &self.def.write {
571 Some(WriteFn::SharedSync(func)) => func(model.shared(), dev, value.clone())?,
572 Some(WriteFn::ExclusiveSync(func)) => func(model.exclusive()?, dev, value.clone())?,
573 Some(WriteFn::SharedAsync(func)) => func(model.shared(), dev, value.clone()).await?,
574 Some(WriteFn::ExclusiveAsync(func)) => func(model.exclusive()?, dev, value.clone()).await?,
575 None if self.def.read.is_none() => return Err(self.no_handler_err(dev)),
576 None => return Err(Error::new(ErrorKind::AttrNotWritable)
577 .with_append_description(format_args!("Attribute {:?} of the device {:?}",
578 self.name(), dev.name()))),
579 }
580
581 *self.w_value.lock().unwrap() = Some(value);
585 *self.w_time.lock().unwrap() = Some(Instant::now());
586 self.setup.lock().unwrap().mem_error = None;
587 Ok(())
588
589 }
590
591 fn check_limits(&self, dev: &Dev<M>, value: &Value) -> Result<()> {
595
596 let range = self.setup.lock().unwrap().config.range;
597 if !any_number(value, |number| outside(&range, number)) {
598 return Ok(());
599 }
600
601 let bound = |bound: Option<AttrNumber>| bound.map_or_else(|| "-".to_string(), |bound| bound.to_string());
602 Err(Error::new(ErrorKind::Reason(reason::API_WAttrOutsideLimit))
603 .with_description(format!("The value written to the attribute {:?} of the device {:?} is \
604 outside of its range [{}, {}]", self.name(), dev.name(), bound(range.min), bound(range.max))))
605
606 }
607
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
613pub enum AttrAlarm {
614 Alarm,
616 Warning,
618 Delta,
620 Device(AttrQuality),
622}
623
624impl AttrAlarm {
625
626 pub fn quality(self) -> AttrQuality {
628 match self {
629 Self::Alarm | Self::Delta => AttrQuality::Alarm,
630 Self::Warning => AttrQuality::Warning,
631 Self::Device(quality) => quality,
632 }
633 }
634
635 pub fn describe(self, name: &str) -> String {
637 match self {
638 Self::Alarm => format!("Alarm: the attribute {name} is outside of its alarm range"),
639 Self::Warning => format!("Warning: the attribute {name} is outside of its warning range"),
640 Self::Delta => format!("Alarm: the attribute {name} is too far from the value written to it"),
641 Self::Device(AttrQuality::Warning) => format!("Warning: the attribute {name} is in WARNING"),
642 Self::Device(_) => format!("Alarm: the attribute {name} is in ALARM"),
643 }
644 }
645
646}
647
648fn any_number(value: &Value, mut pred: impl FnMut(AttrNumber) -> bool) -> bool {
655
656 macro_rules! any {
657 ($format:expr) => {
658 match $format {
659 ValueFormat::Scalar(data) => data.iter().any(|number| pred(number.into())),
660 ValueFormat::Spectrum(data) => data.iter().any(|number| pred(number.into())),
661 ValueFormat::Image(data) => data.iter().any(|number| pred(number.into())),
662 }
663 };
664 }
665
666 match value {
667 Value::Octet(format) => any!(format),
668 Value::UShort(format) => any!(format),
669 Value::Short(format) => any!(format),
670 Value::ULong(format) => any!(format),
671 Value::Long(format) => any!(format),
672 Value::ULong64(format) => any!(format),
673 Value::Long64(format) => any!(format),
674 Value::Float(format) => any!(format),
675 Value::Double(format) => any!(format),
676 _ => false,
677 }
678
679}
680
681fn outside(range: &AttrRangeConfig, number: AttrNumber) -> bool {
684 let below = range.min.is_some_and(|min| number.partial_cmp(&min).is_none_or(Ordering::is_lt));
685 let above = range.max.is_some_and(|max| number.partial_cmp(&max).is_none_or(Ordering::is_gt));
686 below || above
687}
688
689fn drifted(read: &Value, written: &Value, delta: AttrNumber) -> bool {
692
693 let mut written_numbers = Vec::new();
694 any_number(written, |number| {
695 written_numbers.push(number.as_f64());
696 false
697 });
698
699 let delta = delta.as_f64();
700 let mut written_numbers = written_numbers.into_iter();
701 any_number(read, |number| written_numbers.next()
702 .is_some_and(|written| (number.as_f64() - written).abs() > delta))
703
704}
705
706pub type PropsResult<'a> = std::result::Result<&'a [Named<Spectrum<str>>], &'a Error>;
712
713const NOT_SPECIFIED: &str = "Not specified";
716const NOT_A_NUMBER: &str = "NaN";
717
718struct ConfigLoader {
721 config: AttrConfig,
722 kind: Option<AttrDataKind>,
725 delta_value: Option<AttrNumber>,
728 delta_time: Option<Duration>,
729 errors: Vec<String>,
731}
732
733impl ConfigLoader {
734
735 fn new(config: &AttrConfig, kind: Option<AttrDataKind>) -> Self {
736 Self {
737 config: config.clone(),
738 kind,
739 delta_value: config.delta.map(|delta| delta.value),
740 delta_time: config.delta.and_then(|delta| delta.time),
741 errors: Vec::new(),
742 }
743 }
744
745 fn apply_level(&mut self, level: &str, props: PropsResult<'_>) {
747 match props {
748 Ok(props) => {
749 for prop in props {
750 if let Err(err) = self.apply(prop.name.value(), &prop.value) {
751 self.errors.push(format!("{level} property {:?}: {err}", prop.name.value()));
752 }
753 }
754 }
755 Err(err) => self.errors.push(format!("{level} properties: {}", err.description())),
756 }
757 }
758
759 fn apply(&mut self, name: &str, values: &Spectrum<str>) -> std::result::Result<(), String> {
763 match ConfigProp::from_name(name) {
764 Some(prop) => self.apply_prop(prop, values),
765 None => Ok(()),
766 }
767 }
768
769 fn apply_prop(&mut self, prop: ConfigProp, values: &Spectrum<str>) -> std::result::Result<(), String> {
772
773 if !prop.supported_by(self.kind) {
774 return match values.iter().all(|value| is_unset(value.trim())) {
775 true => Ok(()),
776 false => Err(unsupported(self.kind)),
777 };
778 }
779
780 let config = &mut self.config;
781 let limits = limit_kind(self.kind);
782
783 match prop {
784 ConfigProp::Description => config.doc = text(values),
785 ConfigProp::Label => config.label = text(values),
786 ConfigProp::Unit => config.unit = text(values),
787 ConfigProp::StandardUnit => config.standard_unit = number(values)?,
788 ConfigProp::DisplayUnit => config.display_unit = text(values),
789 ConfigProp::Format => config.display_format = text(values),
790 ConfigProp::MinValue => config.range.min = limit(values, limits)?,
791 ConfigProp::MaxValue => config.range.max = limit(values, limits)?,
792 ConfigProp::MinAlarm => config.alarm.min = limit(values, limits)?,
793 ConfigProp::MaxAlarm => config.alarm.max = limit(values, limits)?,
794 ConfigProp::MinWarning => config.warning.min = limit(values, limits)?,
795 ConfigProp::MaxWarning => config.warning.max = limit(values, limits)?,
796 ConfigProp::DeltaVal => self.delta_value = limit(values, limits)?,
797 ConfigProp::DeltaT => self.delta_time = duration(values)?.filter(|time| !time.is_zero()),
799 ConfigProp::RelChange => config.change_event.rel_change = change(values)?,
800 ConfigProp::AbsChange => config.change_event.abs_change = change(values)?,
801 ConfigProp::EventPeriod => config.periodic_event.period = duration(values)?,
802 ConfigProp::ArchiveRelChange => config.archive_event.rel_change = change(values)?,
803 ConfigProp::ArchiveAbsChange => config.archive_event.abs_change = change(values)?,
804 ConfigProp::ArchivePeriod => config.archive_event.period = duration(values)?,
805 ConfigProp::EnumLabels => config.enum_labels = labels(values),
806 }
807
808 Ok(())
809
810 }
811
812 fn finish(self) -> (AttrConfig, Option<String>) {
814 let mut config = self.config;
815 config.delta = self.delta_value.map(|value| AttrDeltaConfig { value, time: self.delta_time });
816 let error = (!self.errors.is_empty()).then(|| self.errors.join(", "));
817 (config, error)
818 }
819
820}
821
822#[derive(Debug, Clone, Copy, PartialEq)]
826enum ConfigPropValue<'a> {
827 Text(Option<&'a str>),
828 Number(Option<f64>),
829 Limit(Option<AttrNumber>),
830 Duration(Option<Duration>),
831 Labels(&'a [Cow<'static, str>]),
832}
833
834impl ConfigPropValue<'_> {
835
836 fn is_unset(self) -> bool {
838 match self {
839 Self::Text(text) => text.is_none(),
840 Self::Number(number) => number.is_none(),
841 Self::Limit(limit) => limit.is_none(),
842 Self::Duration(duration) => duration.is_none(),
843 Self::Labels(labels) => labels.is_empty(),
844 }
845 }
846
847 fn to_values(self) -> Vec<String> {
851 if self.is_unset() {
852 return vec![NOT_SPECIFIED.to_string()];
853 }
854 match self {
855 Self::Text(text) => text.unwrap_or_default().split('\n').map(str::to_string).collect(),
856 Self::Number(number) => number.iter().map(f64::to_string).collect(),
857 Self::Limit(limit) => limit.iter().map(AttrNumber::to_string).collect(),
858 Self::Duration(duration) => duration.iter().map(|duration| (duration.as_secs_f64() * 1000.0).to_string()).collect(),
859 Self::Labels(labels) => labels.iter().map(|label| label.to_string()).collect(),
860 }
861 }
862
863}
864
865fn prop_value(config: &AttrConfig, prop: ConfigProp) -> ConfigPropValue<'_> {
867 match prop {
868 ConfigProp::Description => ConfigPropValue::Text(config.doc.as_deref()),
869 ConfigProp::Label => ConfigPropValue::Text(config.label.as_deref()),
870 ConfigProp::Unit => ConfigPropValue::Text(config.unit.as_deref()),
871 ConfigProp::StandardUnit => ConfigPropValue::Number(config.standard_unit),
872 ConfigProp::DisplayUnit => ConfigPropValue::Text(config.display_unit.as_deref()),
873 ConfigProp::Format => ConfigPropValue::Text(config.display_format.as_deref()),
874 ConfigProp::MinValue => ConfigPropValue::Limit(config.range.min),
875 ConfigProp::MaxValue => ConfigPropValue::Limit(config.range.max),
876 ConfigProp::MinAlarm => ConfigPropValue::Limit(config.alarm.min),
877 ConfigProp::MaxAlarm => ConfigPropValue::Limit(config.alarm.max),
878 ConfigProp::MinWarning => ConfigPropValue::Limit(config.warning.min),
879 ConfigProp::MaxWarning => ConfigPropValue::Limit(config.warning.max),
880 ConfigProp::DeltaVal => ConfigPropValue::Limit(config.delta.map(|delta| delta.value)),
881 ConfigProp::DeltaT => ConfigPropValue::Duration(config.delta.and_then(|delta| delta.time)),
882 ConfigProp::RelChange => ConfigPropValue::Number(config.change_event.rel_change),
883 ConfigProp::AbsChange => ConfigPropValue::Number(config.change_event.abs_change),
884 ConfigProp::EventPeriod => ConfigPropValue::Duration(config.periodic_event.period),
885 ConfigProp::ArchiveRelChange => ConfigPropValue::Number(config.archive_event.rel_change),
886 ConfigProp::ArchiveAbsChange => ConfigPropValue::Number(config.archive_event.abs_change),
887 ConfigProp::ArchivePeriod => ConfigPropValue::Duration(config.archive_event.period),
888 ConfigProp::EnumLabels => ConfigPropValue::Labels(&config.enum_labels),
889 }
890}
891
892#[derive(Debug, Clone, Copy, PartialEq, Eq)]
895pub enum ConfigProp {
896 Description,
897 Label,
898 Unit,
899 StandardUnit,
900 DisplayUnit,
901 Format,
902 MinValue,
903 MaxValue,
904 MinAlarm,
905 MaxAlarm,
906 MinWarning,
907 MaxWarning,
908 DeltaVal,
909 DeltaT,
910 RelChange,
911 AbsChange,
912 EventPeriod,
913 ArchiveRelChange,
914 ArchiveAbsChange,
915 ArchivePeriod,
916 EnumLabels,
917}
918
919impl ConfigProp {
920
921 const ALL: [Self; 21] = [
923 Self::Description,
924 Self::Label,
925 Self::Unit,
926 Self::StandardUnit,
927 Self::DisplayUnit,
928 Self::Format,
929 Self::MinValue,
930 Self::MaxValue,
931 Self::MinAlarm,
932 Self::MaxAlarm,
933 Self::MinWarning,
934 Self::MaxWarning,
935 Self::DeltaVal,
936 Self::DeltaT,
937 Self::RelChange,
938 Self::AbsChange,
939 Self::EventPeriod,
940 Self::ArchiveRelChange,
941 Self::ArchiveAbsChange,
942 Self::ArchivePeriod,
943 Self::EnumLabels,
944 ];
945
946 pub fn name(self) -> &'static str {
948 match self {
949 Self::Description => "description",
950 Self::Label => "label",
951 Self::Unit => "unit",
952 Self::StandardUnit => "standard_unit",
953 Self::DisplayUnit => "display_unit",
954 Self::Format => "format",
955 Self::MinValue => "min_value",
956 Self::MaxValue => "max_value",
957 Self::MinAlarm => "min_alarm",
958 Self::MaxAlarm => "max_alarm",
959 Self::MinWarning => "min_warning",
960 Self::MaxWarning => "max_warning",
961 Self::DeltaVal => "delta_val",
962 Self::DeltaT => "delta_t",
963 Self::RelChange => "rel_change",
964 Self::AbsChange => "abs_change",
965 Self::EventPeriod => "event_period",
966 Self::ArchiveRelChange => "archive_rel_change",
967 Self::ArchiveAbsChange => "archive_abs_change",
968 Self::ArchivePeriod => "archive_period",
969 Self::EnumLabels => "enum_labels",
970 }
971 }
972
973 fn supported_by(self, kind: Option<AttrDataKind>) -> bool {
977 let numeric = !matches!(kind, None | Some(AttrDataKind::Bool | AttrDataKind::State | AttrDataKind::String | AttrDataKind::Encoded));
978 match self {
979 Self::MinAlarm |
980 Self::MaxAlarm |
981 Self::MinWarning |
982 Self::MaxWarning => numeric && kind != Some(AttrDataKind::Enum),
983 Self::MinValue |
984 Self::MaxValue |
985 Self::DeltaVal |
986 Self::DeltaT |
987 Self::RelChange |
988 Self::AbsChange |
989 Self::ArchiveRelChange |
990 Self::ArchiveAbsChange => numeric,
991 _ => true,
992 }
993 }
994
995 fn from_name(name: &str) -> Option<Self> {
998 Self::ALL.into_iter().find(|prop| name.eq_ignore_ascii_case(prop.name()))
999 }
1000
1001}
1002
1003fn is_unset(value: &str) -> bool {
1005 value.is_empty() || value.eq_ignore_ascii_case(NOT_SPECIFIED) || value.eq_ignore_ascii_case(NOT_A_NUMBER)
1006}
1007
1008fn text(values: &Spectrum<str>) -> Option<Cow<'static, str>> {
1010 let text = values.iter().collect::<Vec<_>>().join("\n");
1011 (!is_unset(text.trim())).then_some(Cow::Owned(text))
1012}
1013
1014fn single(values: &Spectrum<str>) -> std::result::Result<Option<&str>, String> {
1016 let mut iter = values.iter().map(str::trim);
1017 match (iter.next(), iter.next()) {
1018 (None, _) => Ok(None),
1019 (Some(value), None) => Ok((!is_unset(value)).then_some(value)),
1020 (Some(_), Some(_)) => Err(format!("expected a single value, got {}", values.len())),
1021 }
1022}
1023
1024fn parse_number(value: &str) -> std::result::Result<f64, String> {
1026 value.parse::<f64>().ok()
1027 .filter(|value| value.is_finite())
1028 .ok_or_else(|| format!("{value:?} is not a number"))
1029}
1030
1031fn number(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1033 single(values)?.map(parse_number).transpose()
1034}
1035
1036fn limit(values: &Spectrum<str>, kind: LimitKind) -> std::result::Result<Option<AttrNumber>, String> {
1039
1040 let Some(value) = single(values)? else {
1041 return Ok(None);
1042 };
1043
1044 let typed = match kind {
1045 LimitKind::Long => value.parse::<i64>().ok().map(AttrNumber::Long),
1046 LimitKind::ULong => value.parse::<u64>().ok().map(AttrNumber::ULong),
1047 LimitKind::Double => None,
1048 };
1049
1050 match typed {
1051 Some(typed) => Ok(Some(typed)),
1052 None => parse_number(value).map(|value| Some(AttrNumber::Double(value))),
1053 }
1054
1055}
1056
1057fn duration(values: &Spectrum<str>) -> std::result::Result<Option<Duration>, String> {
1059
1060 let Some(millis) = number(values)? else {
1061 return Ok(None);
1062 };
1063
1064 Duration::try_from_secs_f64(millis / 1000.0)
1065 .map(Some)
1066 .map_err(|_| format!("{millis} is not a duration in milliseconds"))
1067
1068}
1069
1070fn change(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1075
1076 let changes = values.iter()
1077 .flat_map(|value| value.split(','))
1078 .map(str::trim)
1079 .filter(|value| !is_unset(value))
1080 .collect::<Vec<_>>();
1081
1082 if changes.len() > 2 {
1083 return Err(format!("expected at most two values, got {}", changes.len()));
1084 }
1085
1086 let mut min = None::<f64>;
1087 for change in changes {
1088 let change = parse_number(change)?.abs();
1089 min = Some(min.map_or(change, |min| min.min(change)));
1090 }
1091
1092 Ok(min)
1093
1094}
1095
1096fn labels(values: &Spectrum<str>) -> Vec<Cow<'static, str>> {
1098 match single(values) {
1099 Ok(None) => Vec::new(),
1100 _ => values.iter().map(|label| Cow::Owned(label.to_string())).collect(),
1101 }
1102}
1103
1104fn limit_kind(kind: Option<AttrDataKind>) -> LimitKind {
1107 match kind {
1108 Some(AttrDataKind::Short | AttrDataKind::Long | AttrDataKind::Long64 | AttrDataKind::Enum) => LimitKind::Long,
1109 Some(AttrDataKind::Octet | AttrDataKind::UShort | AttrDataKind::ULong | AttrDataKind::ULong64) => LimitKind::ULong,
1110 _ => LimitKind::Double,
1112 }
1113}
1114
1115pub fn unsupported_prop(config: &AttrConfig, kind: Option<AttrDataKind>) -> Option<ConfigProp> {
1118 ConfigProp::ALL.into_iter().find(|&prop| !prop.supported_by(kind) && !prop_value(config, prop).is_unset())
1119}
1120
1121pub fn unsupported(kind: Option<AttrDataKind>) -> String {
1123 match kind {
1124 Some(kind) => format!("not supported by an attribute of type {kind:?}"),
1125 None => "not supported by an attribute of no type".to_string(),
1126 }
1127}
1128
1129#[derive(Debug, Clone, Copy)]
1131enum LimitKind {
1132 Double,
1133 Long,
1134 ULong,
1135}
1136
1137#[derive(Debug, Default, PartialEq)]
1144pub struct ConfigChanges {
1145 pub put: Vec<(ConfigProp, Vec<String>)>,
1147 pub del: Vec<ConfigProp>,
1149}
1150
1151impl ConfigChanges {
1152 pub fn is_empty(&self) -> bool {
1153 self.put.is_empty() && self.del.is_empty()
1154 }
1155}
1156
1157fn copy_prop(dst: &mut AttrConfig, src: &AttrConfig, prop: ConfigProp) {
1163 match prop {
1164 ConfigProp::Description => dst.doc = src.doc.clone(),
1165 ConfigProp::Label => dst.label = src.label.clone(),
1166 ConfigProp::Unit => dst.unit = src.unit.clone(),
1167 ConfigProp::StandardUnit => dst.standard_unit = src.standard_unit,
1168 ConfigProp::DisplayUnit => dst.display_unit = src.display_unit.clone(),
1169 ConfigProp::Format => dst.display_format = src.display_format.clone(),
1170 ConfigProp::MinValue => dst.range.min = src.range.min,
1171 ConfigProp::MaxValue => dst.range.max = src.range.max,
1172 ConfigProp::MinAlarm => dst.alarm.min = src.alarm.min,
1173 ConfigProp::MaxAlarm => dst.alarm.max = src.alarm.max,
1174 ConfigProp::MinWarning => dst.warning.min = src.warning.min,
1175 ConfigProp::MaxWarning => dst.warning.max = src.warning.max,
1176 ConfigProp::DeltaVal => {
1177 let time = dst.delta.and_then(|delta| delta.time);
1178 dst.delta = src.delta.map(|delta| AttrDeltaConfig { value: delta.value, time });
1179 }
1180 ConfigProp::DeltaT => {
1181 if let Some(delta) = &mut dst.delta {
1182 delta.time = src.delta.and_then(|delta| delta.time);
1183 }
1184 }
1185 ConfigProp::RelChange => dst.change_event.rel_change = src.change_event.rel_change,
1186 ConfigProp::AbsChange => dst.change_event.abs_change = src.change_event.abs_change,
1187 ConfigProp::EventPeriod => dst.periodic_event.period = src.periodic_event.period,
1188 ConfigProp::ArchiveRelChange => dst.archive_event.rel_change = src.archive_event.rel_change,
1189 ConfigProp::ArchiveAbsChange => dst.archive_event.abs_change = src.archive_event.abs_change,
1190 ConfigProp::ArchivePeriod => dst.archive_event.period = src.archive_event.period,
1191 ConfigProp::EnumLabels => dst.enum_labels = src.enum_labels.clone(),
1192 }
1193}
1194
1195fn incoherence(config: &AttrConfig) -> Option<String> {
1198 for (what, range) in [
1199 ("value", &config.range),
1200 ("alarm", &config.alarm),
1201 ("warning", &config.warning)
1202 ] {
1203 if let (Some(min), Some(max)) = (range.min, range.max) {
1204 if !(min < max) {
1205 return Some(format!("the minimum of its {what} range ({min}) is not below its maximum ({max})"));
1206 }
1207 }
1208 }
1209 None
1210}
1211
1212const MEM_VALUE: &str = "__value";
1219const MEM_NOT_USED: &str = "Not used yet";
1221
1222fn mem_value_str(value: &Value) -> std::result::Result<String, String> {
1228 fn scalar<T: Element + ?Sized>(format: &ValueFormat<T>) -> std::result::Result<T::Ref<'_>, String> {
1229 format.scalar().map(|scalar| scalar.value()).ok_or_else(|| "only a scalar can be memorized".to_string())
1230 }
1231 Ok(match value {
1232 Value::Bool(format) => scalar(format)?.to_string(),
1233 Value::Octet(format) => scalar(format)?.to_string(),
1234 Value::UShort(format) => scalar(format)?.to_string(),
1235 Value::Short(format) => scalar(format)?.to_string(),
1236 Value::ULong(format) => scalar(format)?.to_string(),
1237 Value::Long(format) => scalar(format)?.to_string(),
1238 Value::ULong64(format) => scalar(format)?.to_string(),
1239 Value::Long64(format) => scalar(format)?.to_string(),
1240 Value::Float(format) => scalar(format)?.to_string(),
1241 Value::Double(format) => scalar(format)?.to_string(),
1242 Value::Enum(format) => scalar(format)?.0.to_string(),
1243 Value::String(format) => scalar(format)?.to_string(),
1244 _ => return Err("a value of that kind cannot be memorized".to_string()),
1245 })
1246}
1247
1248fn parse_mem_value(kind: Option<&AttrDataKind>, values: &Spectrum<str>) -> std::result::Result<Option<Value>, String> {
1251
1252 let mut iter = values.iter();
1253 let value = match (iter.next(), iter.next()) {
1254 (None, _) => return Ok(None),
1255 (Some(value), None) => value,
1256 (Some(_), Some(_)) => return Err(format!("expected a single value, got {}", values.len())),
1257 };
1258
1259 if value == MEM_NOT_USED {
1260 return Ok(None);
1261 }
1262
1263 fn parse<T: std::str::FromStr>(value: &str) -> std::result::Result<T, String> {
1266 value.trim().parse::<T>().map_err(|_| format!("{value:?} is not a value of the attribute"))
1267 }
1268
1269 Ok(Some(match kind {
1270 Some(AttrDataKind::Bool) => match value.trim() {
1271 value if value.eq_ignore_ascii_case("true") || value == "1" => Value::from(true),
1272 value if value.eq_ignore_ascii_case("false") || value == "0" => Value::from(false),
1273 _ => return Err(format!("{value:?} is not a boolean")),
1274 },
1275 Some(AttrDataKind::Octet) => Value::from(parse::<u8>(value)?),
1276 Some(AttrDataKind::UShort) => Value::from(parse::<u16>(value)?),
1277 Some(AttrDataKind::Short) => Value::from(parse::<i16>(value)?),
1278 Some(AttrDataKind::ULong) => Value::from(parse::<u32>(value)?),
1279 Some(AttrDataKind::Long) => Value::from(parse::<i32>(value)?),
1280 Some(AttrDataKind::ULong64) => Value::from(parse::<u64>(value)?),
1281 Some(AttrDataKind::Long64) => Value::from(parse::<i64>(value)?),
1282 Some(AttrDataKind::Float) => Value::from(parse::<f32>(value)?),
1283 Some(AttrDataKind::Double) => Value::from(parse::<f64>(value)?),
1284 Some(AttrDataKind::Enum) => Value::from(DevEnum(parse::<i16>(value)?)),
1285 Some(AttrDataKind::String) => Value::from(value),
1286 _ => return Err("a value of that kind cannot be memorized".to_string()),
1287 }))
1288
1289}
1290
1291#[cfg(test)]
1292mod tests {
1293
1294 use crate::dev::{AttrRangeConfig, AttrChangeEventConfig};
1295
1296 use super::*;
1297
1298 fn current() -> AttrConfig {
1301 AttrConfig {
1302 label: Some("Position".into()),
1303 unit: Some("cm".into()),
1304 range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: None },
1305 ..AttrConfig::default()
1306 }
1307 }
1308
1309 fn changes(change: impl FnOnce(&mut AttrConfig)) -> (ConfigChanges, AttrConfig) {
1312 let attr = Attr::new(Arc::new(AttrDef::<()>::new("Position").kind(AttrDataKind::Long).label("Position")));
1313 attr.setup.lock().unwrap().config = current();
1314 let class = [Named::new("unit".into(), Spectrum::from_strs(["mm"]).unwrap())];
1315 let mut requested = current();
1316 change(&mut requested);
1317 let changes = attr.change_config(&class, &requested).unwrap();
1318 (changes, attr.config())
1319 }
1320
1321 fn put(pairs: &[(ConfigProp, &str)]) -> ConfigChanges {
1322 ConfigChanges {
1323 put: pairs.iter().map(|&(prop, value)| (prop, vec![value.to_string()])).collect(),
1324 del: Vec::new(),
1325 }
1326 }
1327
1328 fn del(props: &[ConfigProp]) -> ConfigChanges {
1329 ConfigChanges { put: Vec::new(), del: props.to_vec() }
1330 }
1331
1332 #[test]
1335 fn range_bounds() {
1336 let range = AttrRangeConfig { min: Some(AttrNumber::Long(-1)), max: Some(AttrNumber::Double(2.5)) };
1337 assert!(!outside(&range, AttrNumber::ULong(2)));
1338 assert!(!outside(&range, AttrNumber::Long(-1)));
1339 assert!(outside(&range, AttrNumber::ULong(3)));
1340 assert!(outside(&range, AttrNumber::Double(f64::NAN)));
1341 let range = AttrRangeConfig { min: None, max: Some(AttrNumber::ULong(u64::MAX - 1)) };
1342 assert!(outside(&range, AttrNumber::ULong(u64::MAX)));
1343 assert!(!outside(&range, AttrNumber::Long(i64::MIN)));
1344 assert!(!any_number(&Value::from("text"), |_| true));
1345 }
1346
1347 #[test]
1350 fn range_incoherence() {
1351 let range = |min: i64, max: i64| AttrRangeConfig { min: Some(AttrNumber::Long(min)), max: Some(AttrNumber::Long(max)) };
1352 assert_eq!(incoherence(&AttrConfig { range: range(-1, 1), ..AttrConfig::default() }), None);
1353 assert!(incoherence(&AttrConfig { warning: range(1, 1), ..AttrConfig::default() }).unwrap().contains("warning"));
1354 assert!(incoherence(&AttrConfig { alarm: range(2, 1), ..AttrConfig::default() }).unwrap().contains("alarm"));
1355 let open = AttrRangeConfig { min: Some(AttrNumber::Long(1)), max: None };
1356 assert_eq!(incoherence(&AttrConfig { range: open, ..AttrConfig::default() }), None);
1357 }
1358
1359 #[test]
1361 fn value_changes() {
1362 assert_eq!(changes(|_| ()).0, ConfigChanges::default());
1363 assert_eq!(changes(|config| config.label = Some("Pos".into())).0, put(&[(ConfigProp::Label, "Pos")]));
1364 assert_eq!(changes(|config| config.range.max = Some(AttrNumber::Long(5))).0, put(&[(ConfigProp::MaxValue, "5")]));
1365 assert_eq!(changes(|config| config.unit = Some("mm".into())).0, del(&[ConfigProp::Unit]));
1366 }
1367
1368 #[test]
1371 fn reset_changes() {
1372 let (unit, config) = changes(|config| config.unit = None);
1373 assert_eq!((unit, config.unit.as_deref()), (del(&[ConfigProp::Unit]), Some("mm")));
1374 let (min, config) = changes(|config| config.range.min = None);
1375 assert_eq!((min, config.range.min), (del(&[ConfigProp::MinValue]), None));
1376 let (label, config) = changes(|config| config.label = None);
1377 assert_eq!((label, config.label.as_deref()), (ConfigChanges::default(), Some("Position")));
1378 }
1379
1380 #[test]
1383 fn unsupported_props() {
1384 let load = |kind, name: &str, value: &str| {
1385 let props = [Named::new(name.into(), Spectrum::from_strs([value]).unwrap())];
1386 let mut loader = ConfigLoader::new(&AttrConfig::default(), Some(kind));
1387 loader.apply_level("device", Ok(&props));
1388 loader.finish().1
1389 };
1390 assert!(load(AttrDataKind::String, "max_alarm", "5").unwrap().contains("not supported by an attribute of type String"));
1391 assert_eq!(load(AttrDataKind::String, "max_alarm", "Not specified"), None);
1392 assert_eq!(load(AttrDataKind::String, "label", "Name"), None);
1393 assert!(load(AttrDataKind::Enum, "max_alarm", "2").is_some());
1394 assert_eq!(load(AttrDataKind::Enum, "max_value", "2"), None);
1395 }
1396
1397 #[test]
1399 fn prop_round_trip() {
1400
1401 let config = AttrConfig {
1402 doc: Some("First line\nSecond line".into()),
1403 standard_unit: Some(0.001),
1404 range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: Some(AttrNumber::Long(5)) },
1405 delta: Some(AttrDeltaConfig { value: AttrNumber::Long(1), time: Some(Duration::from_millis(250)) }),
1406 change_event: AttrChangeEventConfig { rel_change: Some(0.5), abs_change: None },
1407 enum_labels: vec!["Off".into(), "On".into()],
1408 ..AttrConfig::default()
1409 };
1410
1411 let attr = Attr::new(Arc::new(AttrDef::<()>::new("Position").kind(AttrDataKind::Long)));
1412 let changes = attr.change_config(&[], &config).unwrap();
1413 assert_eq!(attr.config(), config);
1414 let props = changes.put.into_iter()
1415 .map(|(prop, values)| Named::new(prop.name().into(), Spectrum::from_strs(values).unwrap()))
1416 .collect::<Vec<_>>();
1417
1418 let mut loader = ConfigLoader::new(&AttrConfig::default(), Some(AttrDataKind::Long));
1419 loader.apply_level("device", Ok(&props));
1420 assert_eq!(loader.finish(), (config, None));
1421
1422 }
1423
1424}