1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! An implementation of the [Tango] distributed control system, written from scratch in
//! Rust, for writing clients and device servers.
//!
//! The protocol actually used to reach a device is a detail of the crate rather than of
//! the code using it: a [`dev::DevProxy`] talks to whatever backend can reach its target,
//! and a [`server::Server`] serves its devices on every backend able to serve them.
//!
//! Every call is blocking, up to the timeout of the proxy or the deadline of the request:
//! there is no runtime to install. Each one also has an asynchronous `*_async` form, which
//! runs on any executor, so that a single thread can wait on many devices at once; either
//! way, the backend multiplexes the concurrent requests over the connection it shares.
//!
//! # Client
//!
//! A client asks a device for what it wants and says which type it expects, one name
//! giving one value and a tuple of names giving as many, in a single request.
//!
//! ```no_run
//! use tango::dev::DevProxy;
//! use tango::error::Result;
//!
//! fn main() -> Result<()> {
//!
//! // The device is looked up in the database given by `TANGO_HOST`, the connection
//! // being made on the first request and kept for the following ones.
//! let dev = DevProxy::new("sys/tg_test/1");
//! println!("{} is {:?}", dev.name()?, dev.state()?);
//!
//! // A reading carries the value, the write value, the quality and the timestamp.
//! let read = dev.read_attr::<f64, _>("double_scalar")?;
//! println!("double_scalar = {:?} ({:?})", read.value, read.quality);
//!
//! dev.write_attr(("double_scalar", 3.14))?;
//!
//! // A command is typed the same way, on both of its sides, `()` being no data.
//! let echo: String = dev.command("DevString", "hello")?;
//! println!("DevString -> {echo}");
//!
//! Ok(())
//! }
//! ```
//!
//! # Server
//!
//! A device server is a plain Rust struct, its model, and the functions of it that the
//! attributes and the commands of the device are defined from. Nothing is implemented on
//! the model and no macro is involved: a read function is given a shared access to it, a
//! write or an exclusive command an exclusive one, and the server takes care of the lock.
//!
//! ```no_run
//! use tango::server::{AttrDef, ClassDef, CommandDef, Dev, ServerBuilder};
//! use tango::error::Result;
//!
//! /// The model of one device, owned by the server serving it.
//! struct Counter {
//! count: i32,
//! }
//!
//! impl Counter {
//!
//! /// Build the model of a device, and give it its interface.
//! fn init(dev: &Dev<Self>) -> Result<Self> {
//!
//! dev.add_attr(AttrDef::new("count")
//! .doc("The current value of the counter.")
//! .read(Counter::count))?;
//!
//! dev.add_command(CommandDef::new("Add")
//! .in_doc("How much to add to the counter.")
//! .out_doc("The value of the counter afterwards.")
//! .exec_exclusive(Counter::add))?;
//!
//! Ok(Self { count: 0 })
//! }
//!
//! /// Read the attribute, sharing the model with the other readers.
//! fn count(&self, _dev: &Dev<Self>) -> Result<i32> {
//! Ok(self.count)
//! }
//!
//! /// Execute the command, alone on the model for the time it takes.
//! fn add(&mut self, _dev: &Dev<Self>, value: i32) -> Result<i32> {
//! self.count += value;
//! Ok(self.count)
//! }
//!
//! }
//!
//! /// Serve the devices the database lists for this server until it is stopped.
//! fn main() -> Result<()> {
//! ServerBuilder::from_args()
//! .class(ClassDef::new("Counter", Counter::init))
//! .run()
//! }
//! ```
//!
//! The server is named after its executable, as cppTango does, and its instance is taken
//! from the command line, so that a `Counter` executable is launched as the `Counter/test`
//! server with `Counter test`, or with `Counter test --dev test/counter/1` to serve a
//! database of its own for testing, with that device in it, see
//! [`server::ServerArgs`].
//!
//! [Tango]: https://www.tango-controls.org/