Skip to main content

Crate tango

Crate tango 

Source
Expand description

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.

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.

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.

Modules§

backend
Generic backend interfaces for devices.
buf
Typed buffers, octets being a Buf<u8, 1>, which is what a backend receives a message into.
corba
The CORBA backend, the protocol every Tango implementation speaks, with the ZMQ event system as part of it.
database
Proxy to database devices.
dev
The client side: DevProxy, the handle to a device, and what it reads and writes.
error
The standard Tango error type.
name
Types for the names Tango gives to things: case-ignored names, and type safe Tango Resource Locators.
prop
Code for parsing and serializing device, class or free property values.
server
This module provides a high-level access to the server-side of Tango.
testing
What testing a client or a server of this crate takes: a database that does not need to be there, and servers run for the time of a test, see TestContext.
value
The values Tango devices exchange: a Value is a Buf of any element type and dimensions Tango has, and what the arguments and results of handlers convert from and into.