Skip to main content

tango/server/
attr.rs

1//! Implementation of attribute instantiations.
2
3use 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
21/// An attribute of a single device, together with the last value written to it, which
22/// Tango keeps aside from the model, and its configuration on that device. Its definition
23/// is shared the way that of a [`Command`] is.
24pub struct Attr<M: Model> {
25    /// Attribute def, behind a pointer because it's a huge structure.
26    def: Arc<AttrDef<M>>,
27    /// The last value written to that attribute, which every read of it gives back
28    /// alongside the read value, so that a client always knows what the device was asked
29    /// for as well as what it does.
30    /// 
31    /// Behind a lock of its own rather than in [`DevMutable`]: it is updated by a write,
32    /// which holds the model, and the interface store is the one lock taken *under* the
33    /// model, never around it.
34    w_value: Mutex<Option<Value>>,
35    /// When that attribute was last written, which the delta alarm gives the device time
36    /// from to reach the value written, see [`AttrDeltaConfig`]. A set point the device
37    /// gives itself, or restores, is not a write and gives it none.
38    w_time: Mutex<Option<Instant>>,
39    /// The configuration of that attribute on its device, see [`AttrSetup`], behind a lock
40    /// of its own for the same reason as the write value.
41    setup: Mutex<AttrSetup>,
42}
43
44/// How an attribute is configured on its device: the configuration of its definition,
45/// which the database overrides once it is asked, see [`Attr::load_config`].
46struct AttrSetup {
47    /// The inner attribute config that is actually being returned when getting attribute
48    /// configuration.
49    config: AttrConfig,
50    /// What the database holds of the attribute that could not be applied, which every
51    /// read and write of it fails with until the device is initialized again: an attribute
52    /// whose bounds or units are not the ones it was configured with is one a client must
53    /// not trust.
54    error: Option<String>,
55    /// Whether the database was asked for that configuration.
56    loaded: bool,
57    /// The memorized value the database holds for an attribute that writes it again, which
58    /// is written once the model is built, see [`Attr::restore`].
59    restore: Option<Value>,
60    /// Why the memorized value could not be restored, which every read fails with until a
61    /// write gives the attribute a value again: a client must not take for the set point a
62    /// value the device was never given back.
63    mem_error: Option<String>,
64}
65
66impl<M: Model> Attr<M> {
67
68    /// A new attribute of a device, from its definition, which nothing was written to yet.
69    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    /// The name of that attribute, as its definition gave it.
89    #[inline]
90    pub fn name(&self) -> &str {
91        &self.def.name
92    }
93
94    /// The configuration of that attribute on its device.
95    pub fn config(&self) -> AttrConfig {
96        self.setup.lock().unwrap().config.clone()
97    }
98
99    /// Whether the database was asked for the configuration of that attribute, which is
100    /// done for the whole device at once, see [`Dev::init`].
101    pub(super) fn is_loaded(&self) -> bool {
102        self.setup.lock().unwrap().loaded
103    }
104
105    /// Whether that attribute has any alarm configured: a bound of its alarm or warning
106    /// range, or a delta, which is what a device in alarm looks at, see
107    /// [`Dev::set_auto_alarm_states`].
108    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    /// Configure that attribute from what the database holds of it: the properties it holds
114    /// for the attribute at the level of the class and then of the device, the device
115    /// overriding the class, and both the definition.
116    /// 
117    /// A level the database could not be asked for, and a property that cannot be applied,
118    /// leave what came before them in place and are kept as the error of the attribute, see
119    /// [`AttrSetup::error`].
120    /// 
121    /// The memorized value of the attribute, which only the device holds, is taken here
122    /// too the first time: it is the set point right away for an attribute that only
123    /// restores it, and is kept to be written for one that writes it again, see
124    /// [`Self::restore`]. Configuring it again, once a client changed its configuration,
125    /// leaves its set point alone.
126    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        // Check if the device attribute properties have a __value property...
155        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    /// Change the configuration of that attribute to the requested one, given what the
185    /// database holds for its class, giving back what that does to what the database holds
186    /// for it on its device.
187    /// 
188    /// The requested configuration is the whole configuration the attribute is to have, which
189    /// a client gets by reading the one it has and changing what it means to, so every field
190    /// is one of:
191    /// 
192    /// - what the attribute has already, which is left alone;
193    /// - a value, stored for the device, unless it is what the device inherits, in which case
194    ///   the device has nothing of its own to store and it is deleted;
195    /// - no value, which brings the field back to what the device inherits, deleting what it
196    ///   has of its own.
197    /// 
198    /// No value therefore never means *no value* when the device inherits one, which is the
199    /// one thing cppTango does that this does not: forcing the library default over the
200    /// defaults of the class.
201    /// 
202    /// The requested configuration is refused, nothing being changed, if it gives a value
203    /// to a property the kind of the attribute does not support, see
204    /// [`ConfigProp::supported_by`]. The configuration the attribute ends up with is refused
205    /// too if it is incoherent, see [`incoherence`], or if it leaves out the set point of a memorized
206    /// attribute, which its next initialization would then fail to restore. Once changed, the
207    /// error of the attribute is what could not be applied of its class, see
208    /// [`AttrSetup::error`].
209    pub(super) fn change_config(&self, class: &[Named<Spectrum<str>>], requested: &AttrConfig) -> std::result::Result<ConfigChanges, String> {
210
211        // What the device inherits, the definition which the class overrides, which is what
212        // the configuration it ends up with starts from: every field is the inherited one
213        // unless the device has one of its own.
214        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                // The device has something of its own only if what it has is not inherited.
235                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        // The configuration is the requested one over the inherited one, so what could not
258        // be applied of the class is all that is left wrong with it.
259        setup.config = config;
260        setup.error = class_error;
261        Ok(changes)
262
263    }
264
265    /// Write the memorized value the database held for that attribute, if it writes it
266    /// again and its device has just been initialized into that model, see
267    /// [`AttrMemorized::RestoreAndWrite`]. The value is written as a client would write
268    /// it, but is not stored again, being the one the database already holds.
269    /// 
270    /// An attribute whose configuration could not be applied is not written: its bounds
271    /// are not the ones it was configured with.
272    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    /// Fail if the configuration of that attribute could not be applied, see
291    /// [`AttrSetup::error`], and, when asked to, if its memorized value could not be
292    /// restored, see [`AttrSetup::mem_error`], which only fails a read: a write is what
293    /// gives the attribute a set point again.
294    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    /// Whether that attribute is memorized, see [`AttrMemorized`].
315    #[inline]
316    pub fn is_memorized(&self) -> bool {
317        self.def.memorized.is_some()
318    }
319
320    /// Give that attribute a new set point without writing it: the write function is not
321    /// called, and the value is what every read gives back as the write value from then
322    /// on, and what the database remembers of a memorized attribute.
323    /// 
324    /// This is for a device whose set point changes by itself, a motor stopped short of its
325    /// target for instance, so that a client and the next initialization find the value
326    /// the device actually works towards.
327    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    /// Store that value as the memorized value of that attribute, if it is memorized.
334    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    /// The access reading that attribute needs to the model.
348    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    /// The access writing that attribute needs to the model.
356    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    /// Whether reading that attribute may block, its read function being synchronous, see
364    /// [`crate::pool::run`].
365    pub(super) fn read_may_block(&self) -> bool {
366        matches!(&self.def.read, Some(ReadFn::SharedSync(_) | ReadFn::ExclusiveSync(_)))
367    }
368
369    /// Whether writing that attribute may block, see [`Self::read_may_block`].
370    pub(super) fn write_may_block(&self) -> bool {
371        matches!(&self.def.write, Some(WriteFn::SharedSync(_) | WriteFn::ExclusiveSync(_)))
372    }
373
374    /// What a client is told of that attribute: what it may read and change of its
375    /// configuration, and what its definition advertises of its data.
376    /// 
377    /// A definition advertising neither a kind nor a format, which only one built out of a
378    /// raw handler and given neither can be, has nothing to tell a client of its data and
379    /// is answered with an error rather than with a guess.
380    /// 
381    /// An attribute whose configuration could not be applied is described all the same,
382    /// with what could be, so that a client can still see what it is configured with.
383    /// 
384    /// TODO: [`AttrMode::ReadWithWrite`] cannot be derived from the handlers, the write
385    /// attribute it is paired with having to be named; it needs its own definition.
386    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            // An attribute that has no handler yet is advertised as anything a handler
392            // could make it, every read and write of it failing, see [`Self::no_handler_err`].
393            (None, None) => AttrMode::ReadWrite,
394        })
395    }
396
397    /// The error of reading or writing that attribute when it has no handler at all, see
398    /// [`Self::info`].
399    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    /// Refuse a subscription to the events of that kind of that attribute if nothing ever
406    /// sends them, and a push of one the device did not declare: the change and archive events are only sent once the device declares it
407    /// pushes them, see [`AttrDef::push_change_event`], and the periodic ones never with no
408    /// polling. The user and alarm events, which only a device pushes, are always allowed,
409    /// as cppTango allows them.
410    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    /// What a client is told of that attribute, accessed in that mode whatever its
433    /// handlers, which is how the State and the Status are described: they are read
434    /// through the device rather than through a handler of their own, see [`Self::info`].
435    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    /// The alarm that reading is in: the one the read function gave it, or else the one
453    /// the configuration of that attribute puts it in, outside of the alarm range, outside
454    /// of the warning range, or too far from the value written for longer than the device
455    /// is given to reach it, see [`AttrDeltaConfig`].
456    /// 
457    /// A quality the read function gave is never overridden, the device always having the
458    /// last word on the quality of its own readings, and it counts the way cppTango counts
459    /// it: an ALARM or a WARNING it gave is an alarm all the same.
460    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    /// Read that attribute, see [`Self::read`], giving back along with the reading the
494    /// alarm its configuration puts it in, if any, see [`Self::alarm`].
495    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    /// Read that attribute on the model, borrowed with the access [`Self::read_access`]
505    /// asked for, its quality being the one its configuration gives it, see
506    /// [`Self::alarm`].
507    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    /// Read that attribute as its device gives it, see [`Self::read`].
512    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            // A write-only attribute has nothing to read but what was written to it, which
523            // is what Tango gives for one: the set point back, and no read value, rather
524            // than an error.
525            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    /// Give a reading that has no write value the last one that was written, and keep the
539    /// write value of one that has it, so that the readings that follow give that one back.
540    /// Either way what a client gets, read or pushed, does not depend on the model having
541    /// kept the value it was written.
542    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    /// Write that attribute on the model, borrowed with the access [`Self::write_access`]
550    /// asked for, keep the value that was written and store it if the attribute is
551    /// memorized.
552    /// 
553    /// A value the device accepted but the database could not store fails the write all
554    /// the same, the way cppTango does: it is the set point of the device, but not the one
555    /// its next initialization finds.
556    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    /// Write that attribute on the model and keep the value that was written, see
563    /// [`Self::write`].
564    async fn write_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
565
566        self.check_limits(dev, &value)?;
567
568        // The handler and the server share the written value, the server keeping it for
569        // the reads.
570        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        // Only a write the device accepted is remembered: a write value a client reads
582        // back is one the device is actually working towards, which also gives back a set
583        // point to an attribute whose memorized value could not be restored.
584        *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    /// Fail if a number of that value is outside the range of values of that attribute,
592    /// which is the range a value written to it has to be in, see [`AttrConfig::range`].
593    /// A value that is not a number has no range to be outside of.
594    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/// The alarm the configuration of an attribute puts a reading of it in, see
611/// [`Attr::alarm`].
612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
613pub enum AttrAlarm {
614    /// Outside of the alarm range.
615    Alarm,
616    /// Outside of the warning range.
617    Warning,
618    /// Too far from the value written.
619    Delta,
620    /// In that quality, which the read function gave it.
621    Device(AttrQuality),
622}
623
624impl AttrAlarm {
625
626    /// The quality of a reading in that alarm.
627    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    /// Why an attribute of that name is in that alarm, as the status of its device says.
636    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
648// =========================== //
649//           Limits            //
650// =========================== //
651
652/// Whether that predicate holds for any number of that value, a value of a kind that is
653/// not a number having none: every element of an array is one of its numbers.
654fn 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
681/// Whether that number is outside of that range, both bounds being part of it, a number
682/// that cannot be compared with a bound, a not-a-number, being outside of any.
683fn 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
689/// Whether a number of the read value is further than that from the number of the value
690/// written at the same place, the two being compared element by element.
691fn 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
706// =========================== //
707//   Database configuration    //
708// =========================== //
709
710/// What the database holds for an attribute at one level, or why it could not be asked.
711pub type PropsResult<'a> = std::result::Result<&'a [Named<Spectrum<str>>], &'a Error>;
712
713// The strings standing for a property that has no value, which bring the field back to no
714// value at all whatever the levels below gave it, see `NOT_SPECIFIED` in the CORBA module.
715const NOT_SPECIFIED: &str = "Not specified";
716const NOT_A_NUMBER: &str = "NaN";
717
718/// The configuration of an attribute being built out of the properties the database holds
719/// for it, starting from the one of its definition.
720struct ConfigLoader {
721    config: AttrConfig,
722    /// The data kind of that attribute, which its limits are read as, and which some
723    /// properties are not supported by, see [`ConfigProp::supported_by`].
724    kind: Option<AttrDataKind>,
725    /// The two halves of the delta alarm, which are two properties that each level may give
726    /// one of, and which only make an alarm together, see [`Self::finish`].
727    delta_value: Option<AttrNumber>,
728    delta_time: Option<Duration>,
729    /// Every property that could not be applied, described.
730    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    /// Apply every property the database holds at that level, named after it in the errors.
746    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    /// Apply the property of that name. A property that is not one of the configuration is
760    /// left alone: the database holds more of an attribute than its configuration, the
761    /// value of a memorized one for instance.
762    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    /// Apply that property, see [`Self::apply`]. A property the kind of the attribute does
770    /// not support is refused unless it has no value.
771    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            // A delta time of zero is how cppTango says there is none.
798            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    /// The configuration that was built, and the error of what could not be applied.
813    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/// The value of one property of a configuration, borrowed from it, which is what two
823/// configurations are compared by, property by property, with nothing written out: two
824/// values that parse the same are the same value, however they were written.
825#[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    /// Whether that property has no value.
837    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    /// The values that property is stored as, a property with no value being stored as
848    /// [`NOT_SPECIFIED`], a text being one value per line and a duration a number of
849    /// milliseconds.
850    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
865/// The value of that property of that configuration, see [`ConfigPropValue`].
866fn 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/// The attribute properties of the database that are part of the configuration of an
893/// attribute, see [`ConfigLoader::apply`].
894#[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    /// Every property.
922    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    /// The name the database holds that property under.
947    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    /// Whether an attribute of that kind supports that property: the bounds, the delta and
974    /// the change criteria are values of the attribute, which only a number has, and an
975    /// enumerated attribute has no alarm, the way cppTango refuses them.
976    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    /// The property of that name, matched ignoring the case like any name the database
996    /// holds, absent for one that is not part of the configuration.
997    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
1003/// Whether that value of a property stands for no value at all.
1004fn 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
1008/// A property that is a text, every line of which is one of its values.
1009fn 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
1014/// The value of a property that has a single one, absent for one that has none.
1015fn 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
1024/// Parse that value as a finite number.
1025fn 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
1031/// A property that is a number.
1032fn number(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1033    single(values)?.map(parse_number).transpose()
1034}
1035
1036/// A property that is a limit, which is a value of the attribute and is read as its type,
1037/// a value that type cannot hold being read as a double, see [`AttrNumber`].
1038fn 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
1057/// A property that is a duration, which the database holds in milliseconds.
1058fn 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
1070/// A property that is the criterion of an event, which Tango allows to be two values, the
1071/// change downwards and the change upwards, either in two values or in one separated by a
1072/// comma. Only one change is kept for both, see [`crate::dev::AttrChangeEventConfig`], and
1073/// the smaller one is: an event is then sent at least as often as either asks for.
1074fn 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
1096/// A property that is the labels of an enumerated attribute, one value each.
1097fn 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
1104/// What the limits of an attribute of that kind are read as: its limits are values of it,
1105/// so they are of its own type.
1106fn 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        // The floating kinds, and the ones that have no limits to speak of.
1111        _ => LimitKind::Double,
1112    }
1113}
1114
1115/// The first property of that configuration that an attribute of that kind does not
1116/// support, see [`ConfigProp::supported_by`].
1117pub 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
1121/// Why a property is refused by an attribute of that kind.
1122pub 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/// The three kinds of number the limits of an attribute may be read as, see [`limit_kind`].
1130#[derive(Debug, Clone, Copy)]
1131enum LimitKind {
1132    Double,
1133    Long,
1134    ULong,
1135}
1136
1137// =========================== //
1138//  Changing the configuration //
1139// =========================== //
1140
1141/// What changing the configuration of an attribute on a device does to what the database
1142/// holds for it on that device, see [`Attr::change_config`].
1143#[derive(Debug, Default, PartialEq)]
1144pub struct ConfigChanges {
1145    /// The properties to store, with their values.
1146    pub put: Vec<(ConfigProp, Vec<String>)>,
1147    /// The properties to delete, the device then inheriting them.
1148    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
1157/// Set that property of a configuration to the one of another.
1158/// 
1159/// The two halves of the delta alarm only make an alarm together, so a delta time given to
1160/// a configuration that has no delta value is lost, as it is when it is loaded, see
1161/// [`ConfigLoader::finish`].
1162fn 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
1195/// What makes that configuration incoherent, if anything: a range whose minimum is not
1196/// below its maximum, which no value could be in, the way cppTango refuses it.
1197fn 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
1212// =========================== //
1213//      Memorized values       //
1214// =========================== //
1215
1216/// The attribute property the memorized value of an attribute is stored under, by the
1217/// device, see [`Attr::store`].
1218const MEM_VALUE: &str = "__value";
1219/// What cppTango stores as the memorized value of an attribute that was never written.
1220const MEM_NOT_USED: &str = "Not used yet";
1221
1222/// The string a memorized value is stored as, which is the one cppTango stores, so that a
1223/// device keeps its set point across servers written in either: a boolean is `true` or
1224/// `false`, an enumerated value its index, and any other number written out.
1225/// 
1226/// Only a scalar can be memorized, see [`super::def::AttrDef::memorized`].
1227fn 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
1248/// The memorized value stored for an attribute of that kind, parsed back, absent for an
1249/// attribute that was never written, see [`mem_value_str`].
1250fn 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    // Read the way cppTango reads it, as a stream would: blanks around a number are not
1264    // part of it, but they are of a string.
1265    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    /// An attribute whose definition gives it a label, whose class is given a unit by the
1299    /// database, and which has a unit and a lower bound of its own on its device.
1300    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    /// The changes of that attribute to the configuration the given function makes of the
1310    /// one it has, along with the configuration it ends up with.
1311    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    /// Numbers against a range whose bounds are of another variant, the widest integers
1333    /// compared exactly, and a not-a-number outside of any range.
1334    #[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    /// A range whose minimum is not below its maximum, an empty one included, and a range
1348    /// open on either side.
1349    #[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    /// A value: stored, unless it is what the device has already, or what it inherits.
1360    #[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    /// No value: what the device inherits, whatever it is, and nothing to do for a field
1369    /// the device has nothing of its own for.
1370    #[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    /// The bounds of a string, refused unless unset, and the alarms of an enumerated
1381    /// attribute, refused when its range is not.
1382    #[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    /// Every field of a configuration stored and loaded back the same.
1398    #[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}